mirror of
https://github.com/stablyai/orca.git
synced 2026-11-06 12:07:58 +00:00
* fix(wsl): the worktree watcher no longer probes a WSL distro's home synchronously at startup For a project inside WSL the watcher resolved the workspace root before seeing the repo is a UNC path it skips. That ran wsl.exe 'echo $HOME' synchronously with a 5 s timeout: on a cold distro it blocked main for 5 s and killed the distro's first boot, after which every wsl.exe call failed with Wsl/Service/E_UNEXPECTED. The watcher now skips WSL repos first and resolves other roots asynchronously. * revert(wsl): drop the transient E_UNEXPECTED retry from #33145 The error came from the watcher's synchronous home probe killing the distro's first boot (fixed in the previous commit). Once that happens the distro answers every wsl.exe call with E_UNEXPECTED for 14+ s, so a 2.5 s retry never recovered it; on the measuring box it failed every attempt. With the trigger gone the retry is dead weight. * fix(wsl): skip mirrored WSL roots without probing; keep the direct-git guard Review follow-ups: a Windows repo whose workspace root mirrors into WSL also probed the distro's home (async, still killed at 5 s) only to skip it, so the watcher now recognizes the mirror and keeps just the Git-common watch. The revert keeps one piece of #33145: a WSL service failure no longer falls back to the login shell and switches the direct git read path off.
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
} from '../../../shared/git-fetch-head-lock'
|
||||
import { pendingWslDriveCheckoutGitRouting } from '../wsl-drive-checkout-git-routing'
|
||||
import { resolveCommand, type ResolvedCommand } from './wsl-command-resolution'
|
||||
import { annotateWslHostFailure, isTransientWslFailure, retryWslRead } from './wsl-host-failure'
|
||||
import { annotateWslHostFailure, isTransientWslFailure } from './wsl-host-failure'
|
||||
import type { GitAdmissionTier, GitExecOptions } from './git-exec-options'
|
||||
import { execFileCapture, execFileCaptureToTermination } from './exec-file-capture'
|
||||
import {
|
||||
@@ -118,11 +118,11 @@ async function gitExecFileAsyncUnlocked(
|
||||
const runCapturedCommand = async (): Promise<{ stdout: string; stderr: string }> => {
|
||||
let result: { stdout: string | Buffer; stderr: string | Buffer }
|
||||
try {
|
||||
result = await retryWslRead(args, options, () => capture(resolved))
|
||||
result = await capture(resolved)
|
||||
} catch (error) {
|
||||
if (
|
||||
directWslGitExitCode(error, resolved) !== null &&
|
||||
// A WSL service hiccup says nothing about the direct git environment.
|
||||
// A WSL service failure says nothing about the direct git environment.
|
||||
!isTransientWslFailure(error) &&
|
||||
!isQuietGitControlFlowExit(error) &&
|
||||
!options.signal?.aborted
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { classifyGitCommand } from '../../../shared/git-command-classification'
|
||||
import { createAbortError } from './abort-error'
|
||||
import type { ResolvedCommand } from './wsl-command-resolution'
|
||||
|
||||
/** wsl.exe's own launch-failure exit, distinct from any status the guest process can return. */
|
||||
@@ -46,95 +44,13 @@ export function annotateWslHostFailure(error: unknown, command: ResolvedCommand)
|
||||
return Object.assign(error, { wslHostFailure: true, wslDistro: distro })
|
||||
}
|
||||
|
||||
// Measured on a cold systemd distro: concurrent first launches intermittently fail before the
|
||||
// guest command starts; a retry a moment later succeeds once the distro is up.
|
||||
const TRANSIENT_WSL_SERVICE_ERROR = /Wsl\/Service\/E_UNEXPECTED\b/
|
||||
const TRANSIENT_WSL_SERVICE_RETRY_DELAYS_MS = [250, 750, 1500] as const
|
||||
// After one call spends its retries, others on that distro fail fast instead of each sleeping.
|
||||
const TRANSIENT_WSL_SERVICE_BREAKER_MS = 5_000
|
||||
const brokenUntilByDistro = new Map<string, number>()
|
||||
// The WSL service's answer while a distro it failed to start is unusable; no guest ran.
|
||||
const WSL_SERVICE_UNEXPECTED = /Wsl\/Service\/E_UNEXPECTED\b/
|
||||
|
||||
/** wsl.exe failed before the guest ran, with an error the WSL service recovers from by itself. */
|
||||
/** wsl.exe failed in the WSL service itself, which says nothing about the guest's git setup. */
|
||||
export function isTransientWslFailure(error: unknown): boolean {
|
||||
if (!error || typeof error !== 'object' || !('wslHostFailure' in error)) {
|
||||
return false
|
||||
}
|
||||
return error instanceof Error && TRANSIENT_WSL_SERVICE_ERROR.test(error.message)
|
||||
}
|
||||
|
||||
function failureDistro(error: unknown): string | null {
|
||||
return error &&
|
||||
typeof error === 'object' &&
|
||||
'wslDistro' in error &&
|
||||
typeof error.wslDistro === 'string'
|
||||
? error.wslDistro.toLowerCase()
|
||||
: null
|
||||
}
|
||||
|
||||
function sleepUnlessAborted(ms: number, signal: AbortSignal | undefined): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const done = (): void => {
|
||||
clearTimeout(timer)
|
||||
signal?.removeEventListener('abort', done)
|
||||
resolve()
|
||||
}
|
||||
const timer = setTimeout(done, ms)
|
||||
signal?.addEventListener('abort', done, { once: true })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-runs a read whose wsl.exe launch hit a transient service error. Reads only: the guest never
|
||||
* started, but a write must not risk running twice on a host that reports failure late. Each
|
||||
* attempt has its own timeout; a timeout is not a host failure, so it is never retried.
|
||||
*/
|
||||
export async function retryTransientWslServiceFailure<T>(
|
||||
run: () => Promise<T>,
|
||||
options: {
|
||||
retryable: boolean
|
||||
signal?: AbortSignal
|
||||
/** False once the caller no longer wants the command (disposed watcher). */
|
||||
canContinue?: () => boolean
|
||||
delaysMs?: readonly number[]
|
||||
now?: () => number
|
||||
}
|
||||
): Promise<T> {
|
||||
const delays = options.delaysMs ?? TRANSIENT_WSL_SERVICE_RETRY_DELAYS_MS
|
||||
const now = options.now ?? Date.now
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
try {
|
||||
return await run()
|
||||
} catch (error) {
|
||||
const distro = failureDistro(error)
|
||||
if (!options.retryable || !isTransientWslFailure(error) || distro === null) {
|
||||
throw error
|
||||
}
|
||||
const delay = delays[attempt]
|
||||
if (delay === undefined || (brokenUntilByDistro.get(distro) ?? 0) > now()) {
|
||||
brokenUntilByDistro.set(distro, now() + TRANSIENT_WSL_SERVICE_BREAKER_MS)
|
||||
throw error
|
||||
}
|
||||
await sleepUnlessAborted(delay, options.signal)
|
||||
if (options.signal?.aborted || options.canContinue?.() === false) {
|
||||
throw createAbortError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function resetTransientWslServiceBreakerForTests(): void {
|
||||
brokenUntilByDistro.clear()
|
||||
}
|
||||
|
||||
/** A git command's first launch, retried per `retryTransientWslServiceFailure` when it is a read. */
|
||||
export function retryWslRead<T>(
|
||||
args: readonly string[],
|
||||
options: { signal?: AbortSignal; canStart?: () => boolean },
|
||||
run: () => Promise<T>
|
||||
): Promise<T> {
|
||||
return retryTransientWslServiceFailure(run, {
|
||||
retryable: classifyGitCommand(args) === 'read',
|
||||
signal: options.signal,
|
||||
canContinue: options.canStart
|
||||
})
|
||||
return error instanceof Error && WSL_SERVICE_UNEXPECTED.test(error.message)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { execFileMock } = vi.hoisted(() => ({ execFileMock: vi.fn() }))
|
||||
|
||||
vi.mock('node:child_process', () => ({
|
||||
execFile: execFileMock,
|
||||
execFileSync: vi.fn(),
|
||||
spawn: vi.fn()
|
||||
}))
|
||||
vi.mock('../../observability/instrumentation', () => ({
|
||||
withGitSpan: (_attributes: unknown, run: (span: unknown) => unknown) =>
|
||||
run({ setAttribute: () => {} })
|
||||
}))
|
||||
vi.mock('../../diagnostics/main-thread-churn-probe', () => ({ recordSubprocessSpawn: vi.fn() }))
|
||||
|
||||
import { gitExecFileAsync } from '../runner'
|
||||
import {
|
||||
peekWslGitReadEnvironment,
|
||||
resetWslGitReadEnvironmentForTests,
|
||||
seedWslGitReadEnvironmentForTests
|
||||
} from '../wsl-git-read-environment'
|
||||
import { _resetGitAdmissionForTests } from './git-subprocess-admission'
|
||||
|
||||
function hostFailure(stdout: string): Error {
|
||||
return Object.assign(new Error('Command failed: wsl.exe -d kali-linux --exec sh -lc ...'), {
|
||||
code: 4294967295,
|
||||
stdout,
|
||||
stderr: ''
|
||||
})
|
||||
}
|
||||
|
||||
async function withPlatform<T>(platform: NodeJS.Platform, run: () => Promise<T>): Promise<T> {
|
||||
const original = process.platform
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: platform })
|
||||
try {
|
||||
return await run()
|
||||
} finally {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: original })
|
||||
}
|
||||
}
|
||||
|
||||
describe('git reads over wsl.exe when the WSL service fails', () => {
|
||||
beforeEach(() => {
|
||||
execFileMock.mockReset()
|
||||
})
|
||||
afterEach(() => {
|
||||
_resetGitAdmissionForTests()
|
||||
})
|
||||
|
||||
const TRANSIENT = 'Catastrophic failure \r\nError code: Wsl/Service/E_UNEXPECTED\r\n'
|
||||
|
||||
function answerWith(results: ({ fail: string } | { stdout: string })[]): void {
|
||||
execFileMock.mockImplementation((_command, _args, _options, callback) => {
|
||||
const child = Object.assign(new EventEmitter(), { pid: 4321, kill: () => {} })
|
||||
const next = results.shift() ?? { stdout: '' }
|
||||
queueMicrotask(() =>
|
||||
'fail' in next
|
||||
? callback?.(hostFailure(next.fail), next.fail, '')
|
||||
: callback?.(null, next.stdout, '')
|
||||
)
|
||||
return child
|
||||
})
|
||||
}
|
||||
|
||||
it('fails a direct read on a WSL service error without the login-shell fallback', async () => {
|
||||
await withPlatform('win32', async () => {
|
||||
const environment = { gitPath: '/usr/bin/git', home: '/home/u', path: '/usr/bin:/bin' }
|
||||
seedWslGitReadEnvironmentForTests('Ubuntu', environment)
|
||||
answerWith([{ fail: TRANSIENT }, { stdout: 'login-shell fallback' }])
|
||||
const failure = await gitExecFileAsync(['status', '--short'], {
|
||||
cwd: String.raw`C:\repo`,
|
||||
preferWslDirectGit: true,
|
||||
wslDistro: 'Ubuntu'
|
||||
}).catch((error: unknown) => error)
|
||||
expect(String(failure)).toContain('Wsl/Service/E_UNEXPECTED')
|
||||
// One direct launch and no login-shell fallback, so the fast path stays on.
|
||||
expect(execFileMock).toHaveBeenCalledOnce()
|
||||
expect(peekWslGitReadEnvironment('Ubuntu')).toEqual(environment)
|
||||
}).finally(resetWslGitReadEnvironmentForTests)
|
||||
})
|
||||
})
|
||||
@@ -1,160 +0,0 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { execFileMock } = vi.hoisted(() => ({ execFileMock: vi.fn() }))
|
||||
|
||||
vi.mock('node:child_process', () => ({
|
||||
execFile: execFileMock,
|
||||
execFileSync: vi.fn(),
|
||||
spawn: vi.fn()
|
||||
}))
|
||||
vi.mock('../../observability/instrumentation', () => ({
|
||||
withGitSpan: (_attributes: unknown, run: (span: unknown) => unknown) =>
|
||||
run({ setAttribute: () => {} })
|
||||
}))
|
||||
vi.mock('../../diagnostics/main-thread-churn-probe', () => ({ recordSubprocessSpawn: vi.fn() }))
|
||||
|
||||
import { gitExecFileAsync } from '../runner'
|
||||
import {
|
||||
peekWslGitReadEnvironment,
|
||||
resetWslGitReadEnvironmentForTests,
|
||||
seedWslGitReadEnvironmentForTests
|
||||
} from '../wsl-git-read-environment'
|
||||
import { _resetGitAdmissionForTests } from './git-subprocess-admission'
|
||||
import { resetTransientWslServiceBreakerForTests } from './wsl-host-failure'
|
||||
|
||||
const WSL_DIAGNOSTIC =
|
||||
'There is no distribution with the supplied name.\r\nError code: Wsl/Service/WSL_E_DISTRO_NOT_FOUND\r\n'
|
||||
|
||||
function hostFailure(stdout: string): Error {
|
||||
return Object.assign(new Error('Command failed: wsl.exe -d kali-linux --exec sh -lc ...'), {
|
||||
code: 4294967295,
|
||||
stdout,
|
||||
stderr: ''
|
||||
})
|
||||
}
|
||||
|
||||
async function withPlatform<T>(platform: NodeJS.Platform, run: () => Promise<T>): Promise<T> {
|
||||
const original = process.platform
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: platform })
|
||||
try {
|
||||
return await run()
|
||||
} finally {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: original })
|
||||
}
|
||||
}
|
||||
|
||||
describe('git reads over wsl.exe on a booting distro', () => {
|
||||
beforeEach(() => {
|
||||
execFileMock.mockReset()
|
||||
resetTransientWslServiceBreakerForTests()
|
||||
})
|
||||
afterEach(() => {
|
||||
_resetGitAdmissionForTests()
|
||||
})
|
||||
|
||||
const TRANSIENT = 'Catastrophic failure \r\nError code: Wsl/Service/E_UNEXPECTED\r\n'
|
||||
|
||||
function answerWith(results: ({ fail: string } | { stdout: string })[]): void {
|
||||
execFileMock.mockImplementation((_command, _args, _options, callback) => {
|
||||
const child = Object.assign(new EventEmitter(), { pid: 4321, kill: () => {} })
|
||||
const next = results.shift() ?? { stdout: '' }
|
||||
queueMicrotask(() =>
|
||||
'fail' in next
|
||||
? callback?.(hostFailure(next.fail), next.fail, '')
|
||||
: callback?.(null, next.stdout, '')
|
||||
)
|
||||
return child
|
||||
})
|
||||
}
|
||||
|
||||
const UNC = '\\\\wsl.localhost\\kali-linux\\home\\paulius\\bugbounty'
|
||||
const listWorktrees = (options: { signal?: AbortSignal } = {}) =>
|
||||
withPlatform('win32', () =>
|
||||
gitExecFileAsync(['worktree', 'list', '--porcelain', '-z'], { cwd: UNC, ...options }).then(
|
||||
(result) => result.stdout,
|
||||
(error: unknown) => error
|
||||
)
|
||||
)
|
||||
|
||||
describe('transient WSL service errors', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
resetTransientWslServiceBreakerForTests()
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('retries a read until the booting distro answers', async () => {
|
||||
answerWith([{ fail: TRANSIENT }, { fail: TRANSIENT }, { stdout: 'worktree /x\0' }])
|
||||
const result = listWorktrees()
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(await result).toBe('worktree /x\0')
|
||||
expect(execFileMock).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('gives up after the bounded retries, then fails fast on that distro for a while', async () => {
|
||||
answerWith([
|
||||
{ fail: TRANSIENT },
|
||||
{ fail: TRANSIENT },
|
||||
{ fail: TRANSIENT },
|
||||
{ fail: TRANSIENT }
|
||||
])
|
||||
const first = listWorktrees()
|
||||
await vi.advanceTimersByTimeAsync(2_500)
|
||||
expect(String(await first)).toContain('Wsl/Service/E_UNEXPECTED')
|
||||
expect(execFileMock).toHaveBeenCalledTimes(4)
|
||||
|
||||
answerWith([{ fail: TRANSIENT }])
|
||||
expect(String(await listWorktrees())).toContain('Wsl/Service/E_UNEXPECTED')
|
||||
expect(execFileMock).toHaveBeenCalledTimes(5)
|
||||
})
|
||||
|
||||
it('stops waiting as soon as the caller aborts', async () => {
|
||||
answerWith([{ fail: TRANSIENT }, { stdout: 'never' }])
|
||||
const controller = new AbortController()
|
||||
const result = listWorktrees({ signal: controller.signal })
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
controller.abort()
|
||||
expect(await result).toMatchObject({ name: 'AbortError' })
|
||||
expect(execFileMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('never re-runs a write, or a read that failed for another reason', async () => {
|
||||
answerWith([{ fail: TRANSIENT }])
|
||||
await withPlatform('win32', () =>
|
||||
gitExecFileAsync(['commit', '-m', 'x'], { cwd: UNC }).catch(() => null)
|
||||
)
|
||||
expect(execFileMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
execFileMock.mockReset()
|
||||
answerWith([{ fail: WSL_DIAGNOSTIC }])
|
||||
await listWorktrees()
|
||||
expect(execFileMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the direct read path when a direct read only hit a transient service error', async () => {
|
||||
vi.useRealTimers()
|
||||
await withPlatform('win32', async () => {
|
||||
const environment = { gitPath: '/usr/bin/git', home: '/home/u', path: '/usr/bin:/bin' }
|
||||
seedWslGitReadEnvironmentForTests('Ubuntu', environment)
|
||||
answerWith([
|
||||
{ fail: TRANSIENT },
|
||||
{ fail: TRANSIENT },
|
||||
{ fail: TRANSIENT },
|
||||
{ fail: TRANSIENT }
|
||||
])
|
||||
const failure = await gitExecFileAsync(['status', '--short'], {
|
||||
cwd: String.raw`C:\repo`,
|
||||
preferWslDirectGit: true,
|
||||
wslDistro: 'Ubuntu'
|
||||
}).catch((error: unknown) => error)
|
||||
expect(String(failure)).toContain('Wsl/Service/E_UNEXPECTED')
|
||||
// Four direct launches and no login-shell fallback, so the fast path stays on.
|
||||
expect(execFileMock).toHaveBeenCalledTimes(4)
|
||||
expect(peekWslGitReadEnvironment('Ubuntu')).toEqual(environment)
|
||||
}).finally(resetWslGitReadEnvironmentForTests)
|
||||
}, 15_000)
|
||||
})
|
||||
@@ -3,10 +3,18 @@ import { join, sep } from 'node:path'
|
||||
import type { GlobalSettings } from '../../shared/global-settings-types'
|
||||
import type { Repo } from '../../shared/repo-types'
|
||||
|
||||
const { readFileMock, realpathMock, statMock } = vi.hoisted(() => ({
|
||||
const { readFileMock, realpathMock, statMock, wslHomeMock, wslHomeAsyncMock } = vi.hoisted(() => ({
|
||||
readFileMock: vi.fn(),
|
||||
realpathMock: vi.fn(),
|
||||
statMock: vi.fn()
|
||||
statMock: vi.fn(),
|
||||
wslHomeMock: vi.fn((): string | null => null),
|
||||
wslHomeAsyncMock: vi.fn(async (): Promise<string | null> => null)
|
||||
}))
|
||||
|
||||
vi.mock('../wsl', async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
getWslHome: wslHomeMock,
|
||||
getWslHomeAsync: wslHomeAsyncMock
|
||||
}))
|
||||
|
||||
vi.mock('node:fs/promises', () => ({
|
||||
@@ -172,6 +180,26 @@ describe('worktree base directory watch target resolution', () => {
|
||||
expect(statMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Real host (win-lowspec): the synchronous probe blocked main 5 s on a cold distro and killed its boot.
|
||||
it('never probes a distro home for a WSL repo or a Windows repo', async () => {
|
||||
const wsl = makeRepo(0, { path: '\\\\wsl.localhost\\Ubuntu\\home\\alice\\project' })
|
||||
const mirrored = makeRepo(1, { path: 'C:\\Users\\alice\\orca' })
|
||||
|
||||
const platform = process.platform
|
||||
// A WSL path is only recognized on Windows.
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: target resolution reads only settings and repos.
|
||||
const store = makeStore([wsl, mirrored]) as never
|
||||
try {
|
||||
await buildWorktreeBaseDirectoryWatchTargets(store)
|
||||
} finally {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: platform })
|
||||
}
|
||||
|
||||
expect(wslHomeMock).not.toHaveBeenCalled()
|
||||
expect(wslHomeAsyncMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// WSL-mirrored worktrees can have Windows Git metadata; UNC root refusal must preserve that watcher.
|
||||
it('keeps the Windows-side git-common watcher when only the workspace root is a WSL UNC path', async () => {
|
||||
const repo = makeRepo(0, {
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
import { isWslUncPath } from '../../shared/wsl-paths'
|
||||
import { mapWithConcurrency } from '../../shared/map-with-concurrency'
|
||||
import { getWorktreeMirrorDistro } from '../project-runtime-git-options'
|
||||
import { computeWorkspaceRoot, getWorktreePathSettings } from './worktree-logic'
|
||||
import {
|
||||
computeWorkspaceRoot,
|
||||
getWorktreePathSettings,
|
||||
workspaceRootMirrorsIntoWsl
|
||||
} from './worktree-logic'
|
||||
import { shouldEmitBoundedWarning } from './bounded-warning-dedupe'
|
||||
import { resolveWorktreeCommonGitDirectory } from './worktree-common-git-directory'
|
||||
import type {
|
||||
@@ -71,20 +75,24 @@ async function maybeAddBaseTarget(
|
||||
settings: GlobalSettings,
|
||||
mirrorDistro: string | undefined
|
||||
): Promise<void> {
|
||||
// UNC watching is unreliable; distro repos have no native target, but mirrored Windows repos retain their Git-common watch.
|
||||
// Why before the root: resolving it probes the distro's home, which boots a stopped distro.
|
||||
if (isWslUncPath(repo.path)) {
|
||||
warnSkippedWslRoot(repo.id, repo.path)
|
||||
return
|
||||
}
|
||||
const pathSettings = getWorktreePathSettings(repo, settings, mirrorDistro)
|
||||
const workspaceRoot = computeWorkspaceRoot(repo.path, pathSettings)
|
||||
const config = {
|
||||
repoId: repo.id,
|
||||
repoName: getRuntimePathBasename(repo.path).replace(/\.git$/, ''),
|
||||
nestWorkspaces: pathSettings.nestWorkspaces
|
||||
}
|
||||
// UNC watching is unreliable; distro repos have no native target, but mirrored Windows repos retain their Git-common watch.
|
||||
if (isWslUncPath(repo.path)) {
|
||||
warnSkippedWslRoot(repo.id, workspaceRoot)
|
||||
return
|
||||
}
|
||||
if (isWslUncPath(workspaceRoot)) {
|
||||
warnSkippedWslRoot(repo.id, workspaceRoot)
|
||||
// A root mirrored into WSL is a UNC path either way; probing the distro's home (which can kill a
|
||||
// booting distro) would only lead to the same skip, so keep just the Git-common watch.
|
||||
const mirrorsIntoWsl = workspaceRootMirrorsIntoWsl(repo.path, pathSettings)
|
||||
const workspaceRoot = mirrorsIntoWsl ? null : computeWorkspaceRoot(repo.path, pathSettings)
|
||||
if (workspaceRoot === null || isWslUncPath(workspaceRoot)) {
|
||||
warnSkippedWslRoot(repo.id, workspaceRoot ?? repo.path)
|
||||
} else {
|
||||
try {
|
||||
if ((await stat(workspaceRoot)).isDirectory()) {
|
||||
|
||||
@@ -126,6 +126,14 @@ export function computeWorkspaceRoot(
|
||||
)
|
||||
}
|
||||
|
||||
/** Whether the workspace root mirrors into a WSL home, which only a wsl.exe probe can resolve. */
|
||||
export function workspaceRootMirrorsIntoWsl(
|
||||
repoPath: string,
|
||||
settings: { workspaceDir: string; wslMirrorDistro?: string }
|
||||
): boolean {
|
||||
return mirrorDistroForWorkspaceRoot(repoPath, settings) !== undefined
|
||||
}
|
||||
|
||||
function mirrorDistroForWorkspaceRoot(
|
||||
repoPath: string,
|
||||
settings: { workspaceDir: string; wslMirrorDistro?: string }
|
||||
|
||||
Reference in New Issue
Block a user