Files
orca/src/shared/wsl-paths.test.ts
T
NeilandNeil 7f63db7d7a fix(git): resolve WSL drvfs Git metadata pointers on a Windows host (#17790)
When Orca's runtime is a WSL distro but the repo sits on a Windows drive, git
inside the distro writes `/mnt/c/...` into a worktree's `.git` gitfile and its
`commondir`, while Orca reads those files back through Win32.
`repo-git-marker-scan` returned the pointer verbatim, Windows read it as
drive-relative `C:\mnt\c\...`, and the worktree was reported `invalid`.

Move that resolver out of `repo-git-marker-scan` into
`src/shared/git-metadata-path.ts` and give it exactly one new case: on win32, a
drvfs pointer resolved against a base path that is not a WSL UNC path now gets
its drive spelling. Every other base/pointer/platform combination is
byte-identical to the deleted helper, verified differentially across a
base x pointer x platform matrix — macOS, Linux and native Windows are unchanged.

`toWindowsWslDrivePath` is factored out of `toWindowsWslPath` so the drvfs
matcher has one home; `toWindowsWslPath` itself is unchanged for all inputs,
including the line terminators JS `.` excludes (fuzzed 2M inputs, 0 divergences).

This changes the marker scan's verdict only. `resolve-git-dir.ts` and the relay's
own copy still `path.resolve` the same `/mnt/c/...` pointer in the Win32
namespace, so a worktree that is now accepted still degrades quietly in conflict
detection, sparse-checkout detection, the diff stamp and worktree listing. Those
parsers are deliberately untouched here; see the PR description.

Co-authored-by: Neil <neil@example.com>
2026-08-31 20:32:46 -07:00

209 lines
7.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
foldWslUncPathCaseInsensitiveParts,
getWslFilesystemBoundaryDistro,
isDrvfsLinuxPath,
isWslUncPath,
parseWslUncPath,
resolveWslRepoWorktreeBasePath,
toWindowsWslDrivePath,
toWindowsWslPath,
toWindowsWslUncPath
} from './wsl-paths'
describe('wsl path helpers', () => {
it('parses modern and legacy WSL UNC paths without platform checks', () => {
expect(parseWslUncPath('\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo')).toEqual({
distro: 'Ubuntu',
linuxPath: '/home/jin/repo'
})
expect(parseWslUncPath('\\\\wsl$\\Debian\\home\\jin')).toEqual({
distro: 'Debian',
linuxPath: '/home/jin'
})
})
it('rejects ordinary Windows and POSIX paths', () => {
expect(isWslUncPath('C:\\Users\\jin\\repo')).toBe(false)
expect(isWslUncPath('/home/jin/repo')).toBe(false)
})
it.each([
['/home/jin/repo', '\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo'],
['/', '\\\\wsl.localhost\\Ubuntu\\'],
['/mnt/c/Users/jin', 'C:\\Users\\jin'],
['/MNT/c/Repo', '\\\\wsl.localhost\\Ubuntu\\MNT\\c\\Repo'],
['/mnt/C/Repo', '\\\\wsl.localhost\\Ubuntu\\mnt\\C\\Repo']
])('converts %s without folding case-sensitive Linux paths', (linuxPath, expected) => {
expect(toWindowsWslPath(linuxPath, 'Ubuntu')).toBe(expected)
})
it.each([
['/mnt/c/Users/jin', 'C:\\Users\\jin'],
['/mnt/d', 'D:\\'],
['/mnt/d/', 'D:\\'],
['/MNT/c/Users/jin', null],
['/mnt/C/Repo', null],
['/home/jin', null],
// A drvfs prefix on a line that still carries a terminator is not a drive path.
['/mnt/c/Users/jin\r', null],
['/mnt/c/Users/jin\n', null],
['/mnt/c/Users/jin\u2028', null],
['/mnt/c/Users/jin\u2029', null]
] as const)('converts the DrvFs path %j without a distro lookup', (linuxPath, expected) => {
expect(toWindowsWslDrivePath(linuxPath)).toBe(expected)
})
it.each(['\r', '\n', '\u2028', '\u2029'])(
'leaves a DrvFs line ending in %j on the distro UNC view',
(terminator) => {
expect(toWindowsWslPath(`/mnt/c/Users/jin${terminator}`, 'Ubuntu')).toBe(
`\\\\wsl.localhost\\Ubuntu\\mnt\\c\\Users\\jin${terminator}`
)
}
)
it('keeps mounted-drive paths on the distro UNC view when requested', () => {
expect(toWindowsWslUncPath('/mnt/c/Users/jin', 'Ubuntu')).toBe(
'\\\\wsl.localhost\\Ubuntu\\mnt\\c\\Users\\jin'
)
})
})
describe('resolveWslRepoWorktreeBasePath', () => {
const WSL_REPO = '\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\src\\repo'
it('resolves an absolute Linux base path into the repo distro (STA-4772)', () => {
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, '/home/jin/project/.orca-worktrees')).toBe(
'\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\project\\.orca-worktrees'
)
expect(resolveWslRepoWorktreeBasePath('\\\\wsl$\\Debian\\srv\\repo', '/srv/trees')).toBe(
'\\\\wsl.localhost\\Debian\\srv\\trees'
)
})
it('keeps drvfs base paths on the distro UNC view so mirroring cannot discard them', () => {
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, '/mnt/d/trees')).toBe(
'\\\\wsl.localhost\\Ubuntu-24.04\\mnt\\d\\trees'
)
})
it('collapses dot segments and trailing slashes so ownership layouts match creation', () => {
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, '/home/jin/src/../trees')).toBe(
'\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\trees'
)
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, '/home/jin/./trees/')).toBe(
'\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\trees'
)
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, '/../..')).toBe(
'\\\\wsl.localhost\\Ubuntu-24.04\\'
)
})
it('keeps UNC, drive, and relative base paths untouched for WSL repos', () => {
expect(
resolveWslRepoWorktreeBasePath(WSL_REPO, '\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\trees')
).toBe('\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\trees')
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, '//wsl.localhost/Ubuntu-24.04/trees')).toBe(
'//wsl.localhost/Ubuntu-24.04/trees'
)
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, 'D:\\trees')).toBe('D:\\trees')
expect(resolveWslRepoWorktreeBasePath(WSL_REPO, '../worktrees')).toBe('../worktrees')
})
it('never rewrites base paths of non-WSL repos', () => {
expect(resolveWslRepoWorktreeBasePath('C:\\src\\repo', '/home/jin/trees')).toBe(
'/home/jin/trees'
)
expect(resolveWslRepoWorktreeBasePath('/srv/repo', '/srv/trees')).toBe('/srv/trees')
expect(resolveWslRepoWorktreeBasePath('//server/share/repo', '/srv/trees')).toBe('/srv/trees')
})
})
describe('foldWslUncPathCaseInsensitiveParts', () => {
it('folds share spelling, distro casing, and separators but not the Linux tail', () => {
expect(foldWslUncPathCaseInsensitiveParts('\\\\WSL$\\Ubuntu\\home\\jin\\Repo')).toBe(
'//wsl.localhost/ubuntu/home/jin/Repo'
)
expect(foldWslUncPathCaseInsensitiveParts('//wsl.localhost/UBUNTU/home/jin/Repo')).toBe(
'//wsl.localhost/ubuntu/home/jin/Repo'
)
})
it('folds drvfs automount tails but not other /mnt entries', () => {
expect(foldWslUncPathCaseInsensitiveParts('\\\\wsl$\\Ubuntu\\mnt\\C\\Users\\Jin')).toBe(
'//wsl.localhost/ubuntu/mnt/c/users/jin'
)
expect(foldWslUncPathCaseInsensitiveParts('\\\\wsl$\\Ubuntu\\mnt\\wsl\\Data')).toBe(
'//wsl.localhost/ubuntu/mnt/wsl/Data'
)
})
it('does not treat a case-variant /MNT dir as the drvfs automount', () => {
expect(foldWslUncPathCaseInsensitiveParts('\\\\wsl$\\Ubuntu\\MNT\\c\\Repo')).toBe(
'//wsl.localhost/ubuntu/MNT/c/Repo'
)
})
it('returns null for non-WSL paths', () => {
expect(foldWslUncPathCaseInsensitiveParts('C:\\Users\\jin')).toBeNull()
expect(foldWslUncPathCaseInsensitiveParts('//server/share/x')).toBeNull()
expect(foldWslUncPathCaseInsensitiveParts('/home/jin')).toBeNull()
})
})
describe('getWslFilesystemBoundaryDistro', () => {
it('names the runtime distro for a Windows drive project running WSL git', () => {
expect(
getWslFilesystemBoundaryDistro({
projectPath: 'C:\\Users\\alice\\orca',
wslRuntimeDistro: 'Ubuntu-24.04'
})
).toBe('Ubuntu-24.04')
})
it('stays silent for a Windows drive project running host git', () => {
for (const wslRuntimeDistro of [undefined, null, '']) {
expect(
getWslFilesystemBoundaryDistro({ projectPath: 'C:/Users/alice/orca', wslRuntimeDistro })
).toBeNull()
}
})
it('stays silent for a project already inside the distro', () => {
expect(
getWslFilesystemBoundaryDistro({
projectPath: '\\\\wsl.localhost\\Ubuntu\\home\\alice\\orca',
wslRuntimeDistro: 'Ubuntu'
})
).toBeNull()
})
// The UNC spelling of drvfs is still the Windows drive, so it crosses the boundary even though
// the path names no runtime preference at all.
it('names the path distro for the UNC spelling of a drvfs mount', () => {
expect(
getWslFilesystemBoundaryDistro({
projectPath: '\\\\wsl.localhost\\Ubuntu\\mnt\\c\\Users\\alice\\orca'
})
).toBe('Ubuntu')
})
it('stays silent for POSIX, relative, and plain UNC share paths', () => {
for (const projectPath of ['/home/alice/orca', 'orca', '\\\\fileserver\\share\\orca']) {
expect(getWslFilesystemBoundaryDistro({ projectPath, wslRuntimeDistro: 'Ubuntu' })).toBeNull()
}
})
})
describe('isDrvfsLinuxPath', () => {
it('accepts the automount root and its descendants only', () => {
expect(isDrvfsLinuxPath('/mnt/c')).toBe(true)
expect(isDrvfsLinuxPath('/mnt/c/Users')).toBe(true)
expect(isDrvfsLinuxPath('/mnt/cdrom')).toBe(false)
// Why: /MNT and /mnt/C are ordinary case-sensitive Linux directories, not the drvfs automount.
expect(isDrvfsLinuxPath('/MNT/c')).toBe(false)
expect(isDrvfsLinuxPath('/mnt/C/Users')).toBe(false)
})
})