Files
orca/src/shared/worktree-ownership.ts
T
Jinjing 102cc09622 Improve worktree import handling (#2677)
* feat: improve worktree import handling

* chore: remove PR docs assets

* rm html
2026-05-23 01:13:33 -07:00

254 lines
7.2 KiB
TypeScript

import {
getRuntimePathBasename,
isWindowsAbsolutePathLike,
normalizeRuntimePathForComparison,
normalizeRuntimePathSeparators,
relativePathInsideRoot
} from './cross-platform-path'
import { parseWslUncPath } from './wsl-paths'
import type {
DetectedWorktree,
ExternalWorktreeVisibility,
GlobalSettings,
OrcaWorkspaceLayout,
Repo,
Worktree,
WorktreeMeta,
WorktreeOwnership
} from './types'
export const EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT = Date.UTC(2026, 4, 23)
export function isLegacyRepoForExternalWorktreeVisibility(repo: Repo): boolean {
if (typeof repo.externalWorktreeVisibilityLegacy === 'boolean') {
return repo.externalWorktreeVisibilityLegacy
}
if (repo.externalWorktreeVisibility === undefined) {
return true
}
if (!Number.isFinite(repo.addedAt)) {
return true
}
return repo.addedAt < EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT
}
export function effectiveExternalWorktreeVisibility(
repo: Pick<Repo, 'externalWorktreeVisibility'>,
isLegacyRepoForVisibility: boolean
): ExternalWorktreeVisibility {
if (repo.externalWorktreeVisibility) {
return repo.externalWorktreeVisibility
}
return isLegacyRepoForVisibility ? 'show' : 'hide'
}
export function buildKnownOrcaWorkspaceLayouts(
settings: Pick<GlobalSettings, 'workspaceDir' | 'nestWorkspaces' | 'workspaceDirHistory'>,
repo?: Pick<Repo, 'path' | 'connectionId'>
): OrcaWorkspaceLayout[] {
const layouts: OrcaWorkspaceLayout[] = []
if (!repo?.connectionId && settings.workspaceDir) {
layouts.push({ path: settings.workspaceDir, nestWorkspaces: settings.nestWorkspaces })
layouts.push(...(settings.workspaceDirHistory ?? []))
}
const wslLayouts = repo ? buildWslWorkspaceLayouts(repo.path, settings) : []
layouts.push(...wslLayouts)
const seen = new Set<string>()
return layouts.filter((layout) => {
const key = `${normalizeRuntimePathForComparison(layout.path)}:${layout.nestWorkspaces}`
if (seen.has(key)) {
return false
}
seen.add(key)
return Boolean(layout.path)
})
}
function buildWslWorkspaceLayouts(
repoPath: string,
settings: Pick<GlobalSettings, 'nestWorkspaces' | 'workspaceDirHistory'>
): OrcaWorkspaceLayout[] {
const parsed = parseWslUncPath(repoPath)
if (!parsed) {
return []
}
const homeMatch = parsed.linuxPath.match(/^\/home\/[^/]+(?:\/|$)/)
const linuxHome = homeMatch?.[0].replace(/\/$/, '')
if (!linuxHome) {
return []
}
const root = `//wsl.localhost/${parsed.distro}${linuxHome}/orca/workspaces`
const historicalModes = (settings.workspaceDirHistory ?? []).map(
(layout) => layout.nestWorkspaces
)
const modes = [settings.nestWorkspaces, ...historicalModes]
return [...new Set(modes)].map((nestWorkspaces) => ({ path: root, nestWorkspaces }))
}
export function classifyWorktreeOwnership(args: {
repo: Repo
worktree: Pick<Worktree, 'path' | 'isMainWorktree'>
meta?: WorktreeMeta
settings: Pick<GlobalSettings, 'workspaceDir' | 'nestWorkspaces' | 'workspaceDirHistory'>
knownOrcaLayouts: OrcaWorkspaceLayout[]
}): WorktreeOwnership {
if (hasStrongOrcaMetadata(args.meta)) {
return 'orca-managed'
}
if (matchesStrongOrcaCreatePath(args.worktree.path, args.knownOrcaLayouts, args.repo)) {
return 'orca-managed'
}
if (isUnderFlatOrUntrustedOrcaRoot(args.worktree.path, args.knownOrcaLayouts)) {
return 'unknown-legacy'
}
if (canClassifyAsExternal(args.worktree.path, args.knownOrcaLayouts)) {
return 'external'
}
return 'unknown-legacy'
}
export function toDetectedWorktree(args: {
repo: Repo
worktree: Worktree
meta?: WorktreeMeta
settings: Pick<GlobalSettings, 'workspaceDir' | 'nestWorkspaces' | 'workspaceDirHistory'>
knownOrcaLayouts: OrcaWorkspaceLayout[]
isLegacyRepoForVisibility?: boolean
}): DetectedWorktree {
const ownership = classifyWorktreeOwnership(args)
const selectedCheckout = areRuntimePathsEqual(args.worktree.path, args.repo.path)
const isLegacyRepoForVisibility =
args.isLegacyRepoForVisibility ?? isLegacyRepoForExternalWorktreeVisibility(args.repo)
const visible = shouldShowWorktree({
worktree: args.worktree,
ownership,
repo: args.repo,
isLegacyRepoForVisibility,
isSelectedCheckout: selectedCheckout
})
return {
...args.worktree,
ownership,
selectedCheckout,
visible
}
}
export function shouldShowWorktree(args: {
worktree: Pick<Worktree, 'path'>
ownership: WorktreeOwnership
repo: Repo
isLegacyRepoForVisibility: boolean
isSelectedCheckout: boolean
}): boolean {
if (args.isSelectedCheckout) {
return true
}
if (args.ownership === 'orca-managed') {
return true
}
if (args.ownership === 'unknown-legacy' && args.isLegacyRepoForVisibility) {
return true
}
return effectiveExternalWorktreeVisibility(args.repo, args.isLegacyRepoForVisibility) === 'show'
}
export function areRuntimePathsEqual(leftPath: string, rightPath: string): boolean {
return (
normalizeRuntimePathForComparison(leftPath) === normalizeRuntimePathForComparison(rightPath)
)
}
function hasStrongOrcaMetadata(meta: WorktreeMeta | undefined): boolean {
return Boolean(
meta?.orcaCreatedAt ||
meta?.createdAt ||
meta?.createdWithAgent ||
meta?.pushTarget ||
meta?.sparseBaseRef ||
meta?.sparsePresetId ||
meta?.preserveBranchOnDelete
)
}
function matchesStrongOrcaCreatePath(
worktreePath: string,
knownOrcaLayouts: OrcaWorkspaceLayout[],
repo: Pick<Repo, 'path'>
): boolean {
const repoName = getRuntimePathBasename(repo.path).replace(/\.git$/i, '')
if (!repoName) {
return false
}
for (const layout of knownOrcaLayouts) {
if (!layout.nestWorkspaces) {
continue
}
const relative = relativePathInsideRoot(layout.path, worktreePath)
if (relative === null) {
continue
}
const segments = splitNormalizedPath(relative)
const caseInsensitive =
isWindowsAbsolutePathLike(layout.path) || isWindowsAbsolutePathLike(worktreePath)
if (
segments.length === 2 &&
normalizePathSegment(segments[0], caseInsensitive) ===
normalizePathSegment(repoName, caseInsensitive) &&
segments[1].length > 0
) {
return true
}
}
return false
}
function isUnderFlatOrUntrustedOrcaRoot(
worktreePath: string,
knownOrcaLayouts: OrcaWorkspaceLayout[]
): boolean {
for (const layout of knownOrcaLayouts) {
const relative = relativePathInsideRoot(layout.path, worktreePath)
if (relative === null) {
continue
}
if (!layout.nestWorkspaces) {
return true
}
}
return false
}
function canClassifyAsExternal(
worktreePath: string,
knownOrcaLayouts: OrcaWorkspaceLayout[]
): boolean {
if (knownOrcaLayouts.length === 0) {
return false
}
for (const layout of knownOrcaLayouts) {
const relative = relativePathInsideRoot(layout.path, worktreePath)
if (relative === null) {
continue
}
return layout.nestWorkspaces
}
return true
}
function splitNormalizedPath(value: string): string[] {
return normalizeRuntimePathSeparators(value).split('/').filter(Boolean)
}
function normalizePathSegment(value: string, caseInsensitive: boolean): string {
const normalized = normalizeRuntimePathSeparators(value)
return caseInsensitive ? normalized.toLowerCase() : normalized
}