mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
Git running inside a WSL distro writes `.git` gitdir pointers, and answers
`status --porcelain`, in the guest namespace. Node reads both back in the
Windows main process, where `/mnt/c/repo/.git` resolves to `C:\mnt\c\repo\.git`
and `/home/me/wt` names nothing at all. Four fs probes were built on those
fabricated paths and always came back "absent":
- `detectConflictOperation`'s four marker probes, so merge/rebase/cherry-pick
badges silently went missing.
- `parseUnmergedEntry`'s compat existence check, so every `deleted_by_us` /
`added_by_them` conflict rendered as 'deleted' regardless of the working tree.
- `findExistingWorktreeSymlinkPaths`' `lstat` from status, so Orca's own shared
symlinks (node_modules and friends) showed as user changes.
- the same `lstat` from the hosted-review dirty preflight, which fails closed:
an unreadable shared symlink read as uncommitted work and blocked PR/MR
creation outright.
`resolveGitDir` computes the host spelling of the worktree once and uses it for
both the gitfile read and the pointer resolve, so a guest-spelled worktree path
is reached at all, and a relative pointer (`worktree.useRelativePaths`, git
2.48+) resolves against a spelling Win32 understands. The pointer itself now
goes through the already-landed `resolveGitMetadataPath`, and the function gains
an optional `{ wslDistro }` for a caller whose base path does not encode a
distro. `detectConflictOperation` forwards it, and the three callers that reach
it -- status-read, the runtime RPC, the `git:conflictOperation` IPC -- pass the
git options they already hold. The return type stays `Promise<string>`.
`resolveWorktreeHostPath` is the same rule applied to a worktree path, used by
status-read for the two working-tree probes and by the review preflight. Both it
and `resolveGitMetadataPath` now treat only a single-leading-slash path as guest
namespace: `//wsl.localhost/...` is already a host UNC spelling, and translating
it prepended a second share prefix.
`readWorktreeDiffStamp` needed the same one-namespace guarantee, since moving
translation inside `resolveGitDir` would otherwise make its HEAD and index real
while the working-tree stat stayed fabricated, letting a settled diff survive
every edit. #17896 landed that change first, so it is no longer in this diff;
its version is a superset and all four components already resolve from one
`hostWorktreePath`. What remains here is the `resolveGitDir` gitfile-pointer
fix that #17896 explicitly deferred, which `worktree-diff-stamp-host-paths.test.ts`
pins.
`getConflictCompatibilityStatus` moves from `existsSync` to async `access`, for
the same reason `detectConflictOperation` did: once these paths are real they
are `\\wsl.localhost\...` shares, and a sync probe per asymmetric conflict
blocks the Electron main thread for a 9p round trip on every status poll.
Per-platform delta:
- native Windows, no WSL: no behavioral change. Nothing here starts with a
single `/`, so no path is translated. An absolute pointer is now returned
verbatim rather than separator-normalized; every consumer re-joins or
normalizes it before use.
- macOS/Linux: no change. Guest-pointer translation is gated to win32, and a
caller-named distro is ignored off Windows.
- Windows + WSL: drvfs pointers and drvfs-spelled worktrees now resolve to their
drive spelling instead of `C:\mnt\...`; a non-drvfs guest path resolves
through the named distro's UNC share, or stays verbatim (ENOENT -> existing
fail-safe) when none is named.
- SSH/relay: none. Those paths return before any of this via the provider
branch; `src/relay/git-handler-status-ops.ts` keeps its own resolveGitDir.
- folder workspaces, GitLab: none. Neither is on these code paths.
85 lines
3.3 KiB
TypeScript
85 lines
3.3 KiB
TypeScript
import { posix, win32 } from 'node:path'
|
|
import { parseWslUncPath, toWindowsWslDrivePath, toWindowsWslPath } from './wsl-paths'
|
|
|
|
// Why the single-leading-slash rule: only `/x` is a guest-namespace path. `//x` is already a host
|
|
// UNC spelling that Win32 opens as-is, and translating it would prepend a second share prefix.
|
|
// Same guard `resolveWslRepoWorktreeBasePath` uses for the same ambiguity.
|
|
const GUEST_ROOTED_PATH = /^\/(?!\/)/
|
|
|
|
export type GitMetadataPathOptions = {
|
|
/** Host that reads the pointer back. Defaults to the current process platform. */
|
|
platform?: NodeJS.Platform
|
|
/** Distro that wrote the pointer, for callers whose base path does not encode one. Windows-only. */
|
|
wslDistro?: string
|
|
}
|
|
|
|
/**
|
|
* Resolve a Git metadata pointer (a `.git` gitfile payload or a `commondir`) in the path namespace
|
|
* of the host that reads it.
|
|
*
|
|
* Why: git running inside WSL writes these pointers in the guest namespace, but Node reads them
|
|
* back through Win32, where a drvfs pointer like `/mnt/c/repo/.git` silently means
|
|
* `C:\mnt\c\repo\.git`. Returns null only for an empty pointer.
|
|
*/
|
|
export function resolveGitMetadataPath(
|
|
basePath: string,
|
|
rawPath: string,
|
|
options: GitMetadataPathOptions = {}
|
|
): string | null {
|
|
const platform = options.platform ?? process.platform
|
|
const value = rawPath.trim()
|
|
if (!value) {
|
|
return null
|
|
}
|
|
if (GUEST_ROOTED_PATH.test(value)) {
|
|
const translated = translateGuestPointer(value, basePath, platform, options.wslDistro)
|
|
if (translated) {
|
|
return translated
|
|
}
|
|
}
|
|
const host = platform === 'win32' ? win32 : posix
|
|
return host.isAbsolute(value) ? value : host.resolve(basePath, value)
|
|
}
|
|
|
|
/**
|
|
* The Win32 spelling of a POSIX-rooted pointer, or null to leave it alone. A WSL UNC base names the
|
|
* distro that wrote the pointer and outranks the caller's guess; failing both, only a drvfs mount
|
|
* has a spelling we can derive.
|
|
*
|
|
* Only a Windows host is translated at all, so a caller-named distro cannot make a POSIX host
|
|
* fabricate a Win32 path. A WSL UNC base is exempt because that spelling only exists on Windows.
|
|
*/
|
|
function translateGuestPointer(
|
|
value: string,
|
|
basePath: string,
|
|
platform: NodeJS.Platform,
|
|
wslDistro: string | undefined
|
|
): string | null {
|
|
const baseDistro = parseWslUncPath(basePath)?.distro
|
|
if (baseDistro) {
|
|
return toWindowsWslPath(value, baseDistro)
|
|
}
|
|
if (platform !== 'win32') {
|
|
return null
|
|
}
|
|
return wslDistro ? toWindowsWslPath(value, wslDistro) : toWindowsWslDrivePath(value)
|
|
}
|
|
|
|
/**
|
|
* The reading host's spelling of a worktree *directory*. Git inside WSL answers in the guest
|
|
* namespace, so a Windows host reopening one of those paths needs the drvfs drive or the distro's
|
|
* UNC share.
|
|
*
|
|
* A directory is not a pointer: `resolveGitMetadataPath` trims because a gitfile payload carries a
|
|
* trailing newline, but a directory name may legally begin or end with whitespace on POSIX. Keep
|
|
* the caller's spelling whenever the resolver only trimmed it, so a host that translates nothing
|
|
* reads exactly the path it was given.
|
|
*/
|
|
export function resolveWorktreeHostPath(
|
|
worktreePath: string,
|
|
options: GitMetadataPathOptions = {}
|
|
): string | null {
|
|
const resolved = resolveGitMetadataPath('', worktreePath, options)
|
|
return resolved === worktreePath.trim() ? worktreePath : resolved
|
|
}
|