import { isWindowsAbsolutePathLike } from '../../shared/cross-platform-path' import type { GitAdmissionTier } from '../../shared/rpc-contract/git-admission-tier-params' import { createGitObjectQuarantine, inheritedObjectStore, pathApiForGitPath, type GitObjectsDirectory } from '../../shared/git-object-quarantine' import { toLinuxPath, toWindowsWslPath } from '../../shared/wsl-paths' import { parseWslPath } from '../wsl' import { usesHostGitForWslLinkedWorktree } from './wsl-linked-worktree-git-routing' import { readRepoCommonDirFromGit } from './worktree-list-reader' export type LocalGitObjectQuarantine = { /** `env` is a full process env for `gitExecFileAsync`, or undefined to run unquarantined. */ run: (command: (env: NodeJS.ProcessEnv | undefined) => Promise) => Promise } /** `objects/` in Git's spelling and, for WSL Git, translated to one the main process can open. */ export function localGitObjectsDirectory( commonDir: string, wslDistro: string | undefined ): GitObjectsDirectory { const hostCommonDir = wslDistro && !isWindowsAbsolutePathLike(commonDir) ? toWindowsWslPath(commonDir, wslDistro) : commonDir // Why: Git < 2.31 prints a relative common dir, resolved against a `C:\` repo path; WSL Git needs `/mnt/c`. const gitCommonDir = wslDistro ? toLinuxPath(commonDir) : commonDir return { hostPath: pathApiForGitPath(hostCommonDir).join(hostCommonDir, 'objects'), gitPath: pathApiForGitPath(gitCommonDir).join(gitCommonDir, 'objects') } } const OBJECT_STORE_VARIABLES = ['GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES'] /** The object-store variables local Git would inherit, or undefined when they cannot be mirrored. */ export function inheritedLocalObjectStore( wslDistro: string | undefined, env: NodeJS.ProcessEnv = process.env, platform: NodeJS.Platform = process.platform ): Pick | undefined { if (platform !== 'win32' || !wslDistro) { return inheritedObjectStore(env) } // Why: wsl.exe forwards only what WSLENV names, maybe path-translated (`/p`); skip rather than guess. const forwarded = new Set((env.WSLENV ?? '').split(':').map((token) => token.split('/')[0])) return OBJECT_STORE_VARIABLES.some((key) => forwarded.has(key) && env[key] !== undefined) ? undefined : {} } /** Scratch object store for throwaway Git writes on the local (native or WSL) host. */ export function createLocalGitObjectQuarantine( repoPath: string, options: { wslDistro?: string signal?: AbortSignal admissionTier?: GitAdmissionTier spawnOnWorkerThread?: boolean } = {} ): LocalGitObjectQuarantine { const wslDistro = parseWslPath(repoPath)?.distro ?? options.wslDistro const quarantine = createGitObjectQuarantine(async () => { const inherited = inheritedLocalObjectStore(wslDistro) if (!inherited) { return undefined } const commonDir = await readRepoCommonDirFromGit(repoPath, { ...(options.wslDistro ? { wslDistro: options.wslDistro } : {}), ...(options.signal ? { signal: options.signal } : {}), ...(options.spawnOnWorkerThread ? { spawnOnWorkerThread: true } : {}), ...(options.admissionTier ? { admissionTier: options.admissionTier } : {}) }) // Probe first to settle linked-worktree routing; host Git may need a different object-store spelling. if (!commonDir || usesHostGitForWslLinkedWorktree(repoPath, options.wslDistro)) { return undefined } return { ...localGitObjectsDirectory(commonDir, wslDistro), ...inherited } }) return { run: (command) => // The WSL runner injects these variables in the command, without WSLENV. quarantine.run((env) => command(env ? { ...process.env, ...env } : undefined)) } }