orca/src/shared/worktree-ownership.test.ts

584 lines
18 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { GlobalSettings, Repo, Worktree, WorktreeMeta } from './types'
import { createAgentScratchWorktreePathMatcher } from './agent-scratch-worktrees'
import {
applyMetadataFallbackVisibility,
buildKnownOrcaWorkspaceLayouts,
classifyWorktreeOwnership,
effectiveExternalWorktreeVisibility,
isLegacyRepoForExternalWorktreeVisibility,
shouldShowWorktree,
toDetectedWorktree,
EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT
} from './worktree-ownership'
const LARGE_WORKSPACE_HISTORY_COUNT = 150_000
function makeRepo(overrides: Partial<Repo> = {}): Repo {
return {
id: 'repo-1',
path: '/repos/app',
displayName: 'app',
badgeColor: '#000',
addedAt: EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT + 1,
kind: 'git',
...overrides
}
}
function makeWorktree(overrides: Partial<Worktree> = {}): Worktree {
return {
id: `repo-1::${overrides.path ?? '/repos/app'}`,
repoId: 'repo-1',
path: '/repos/app',
head: 'abc',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true,
displayName: 'main',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
workspaceStatus: 'todo',
...overrides
}
}
function makeMeta(overrides: Partial<WorktreeMeta> = {}): WorktreeMeta {
return {
displayName: '',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
workspaceStatus: 'todo',
...overrides
}
}
function makeSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings {
return {
workspaceDir: '/orca/workspaces',
nestWorkspaces: true,
workspaceDirHistory: [],
refreshLocalBaseRefOnWorktreeCreate: false,
localBaseRefSuggestionDismissed: false,
branchPrefix: 'none',
branchPrefixCustom: '',
enableGitHubAttribution: false,
theme: 'system',
appFontFamily: 'Geist',
editorAutoSave: false,
editorAutoSaveDelayMs: 1000,
editorMinimapEnabled: false,
markdownReviewToolsEnabled: true,
terminalFontSize: 14,
terminalFontFamily: 'monospace',
terminalFontWeight: 400,
terminalLineHeight: 1.2,
...overrides
} as GlobalSettings
}
describe('worktree ownership classification', () => {
it('treats explicit Orca metadata as managed even outside the workspace root', () => {
const repo = makeRepo()
const settings = makeSettings()
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: '/tmp/outside' }),
meta: makeMeta({ orcaCreatedAt: 1 }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('orca-managed')
})
it('treats nested Orca workspace paths without metadata as external', () => {
const repo = makeRepo()
const settings = makeSettings()
const layouts = buildKnownOrcaWorkspaceLayouts(settings, repo)
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: '/orca/workspaces/app/feature' }),
knownOrcaLayouts: layouts
})
).toBe('external')
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: '/orca/workspaces/other/feature' }),
knownOrcaLayouts: layouts
})
).toBe('external')
})
it('treats explicit Orca creation layout metadata as managed', () => {
const repo = makeRepo()
const settings = makeSettings()
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: '/orca/workspaces/app/feature' }),
meta: makeMeta({
orcaCreationWorkspaceLayout: { path: '/orca/workspaces', nestWorkspaces: true }
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('orca-managed')
})
it('does not treat metadata-free nested workspace paths as Orca-managed for new repos', () => {
const repo = makeRepo({ externalWorktreeVisibility: 'hide' })
const settings = makeSettings()
const detected = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({
path: '/orca/workspaces/app/manual-git-worktree',
isMainWorktree: false
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
expect(detected.ownership).toBe('external')
expect(detected.visible).toBe(false)
})
it('does not treat generic discovery metadata on nested workspace paths as Orca-managed', () => {
const repo = makeRepo({ externalWorktreeVisibility: 'hide' })
const settings = makeSettings()
const detected = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({
path: '/orca/workspaces/app/manual-git-worktree',
isMainWorktree: false
}),
meta: makeMeta({ displayName: 'manual-git-worktree' }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
expect(detected.ownership).toBe('external')
expect(detected.visible).toBe(false)
})
it('keeps nested workspace paths visible for legacy repos without explicit visibility', () => {
const repo = makeRepo()
const settings = makeSettings()
const detected = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({
path: '/orca/workspaces/app/manual-git-worktree',
isMainWorktree: false
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
expect(detected.ownership).toBe('external')
expect(detected.visible).toBe(true)
})
it('hides metadata-free nested workspace paths for legacy repos that hide external worktrees', () => {
const repo = makeRepo({
externalWorktreeVisibility: 'hide',
externalWorktreeVisibilityLegacy: true
})
const settings = makeSettings()
const detected = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({
path: '/orca/workspaces/app/manual-git-worktree',
isMainWorktree: false
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
expect(detected.ownership).toBe('external')
expect(detected.visible).toBe(false)
})
it('treats flat workspace-root descendants as unknown legacy without strong metadata', () => {
const repo = makeRepo()
const settings = makeSettings({ nestWorkspaces: false })
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: '/orca/workspaces/feature' }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('unknown-legacy')
})
it('keeps flat-layout history weak after switching the same root to nested mode', () => {
const repo = makeRepo()
const settings = makeSettings({
nestWorkspaces: true,
workspaceDirHistory: [{ path: '/orca/workspaces', nestWorkspaces: false }]
})
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: '/orca/workspaces/feature' }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('unknown-legacy')
})
it('uses each historical layout nest mode when matching old roots', () => {
const repo = makeRepo()
const settings = makeSettings({
workspaceDir: '/new/workspaces',
workspaceDirHistory: [{ path: '/old/workspaces', nestWorkspaces: true }]
})
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: '/old/workspaces/app/feature' }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('external')
})
it('builds known layouts from large workspace history lists', () => {
const repo = makeRepo()
const workspaceDirHistory = Array.from(
{ length: LARGE_WORKSPACE_HISTORY_COUNT },
(_, index) => ({
path: `/history/workspaces-${index}`,
nestWorkspaces: index % 2 === 0
})
)
const settings = makeSettings({
workspaceDir: '/new/workspaces',
workspaceDirHistory
})
const layouts = buildKnownOrcaWorkspaceLayouts(settings, repo)
expect(layouts).toHaveLength(LARGE_WORKSPACE_HISTORY_COUNT + 1)
expect(layouts[0]).toEqual({ path: '/new/workspaces', nestWorkspaces: true })
expect(layouts[1]).toEqual({ path: '/history/workspaces-0', nestWorkspaces: true })
expect(layouts.at(-1)).toEqual({
path: `/history/workspaces-${LARGE_WORKSPACE_HISTORY_COUNT - 1}`,
nestWorkspaces: false
})
})
it('handles Windows drive casing and separators', () => {
const repo = makeRepo({ path: 'C:\\repos\\App' })
const settings = makeSettings({ workspaceDir: 'C:\\Orca\\Workspaces' })
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({
id: 'repo-1::C:\\ORCA\\WORKSPACES\\App\\Feature',
path: 'C:\\ORCA\\WORKSPACES\\App\\Feature',
isMainWorktree: false
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('external')
})
it('keeps selected linked checkouts visible without trusting Git main-worktree', () => {
const repo = makeRepo({ path: '/repos/app-linked', externalWorktreeVisibility: 'hide' })
const settings = makeSettings()
const selected = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({
path: '/repos/app-linked',
isMainWorktree: false
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
const gitMain = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({
path: '/repos/app-main',
isMainWorktree: true
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
expect(selected.visible).toBe(true)
expect(gitMain.visible).toBe(false)
expect(gitMain.ownership).toBe('external')
})
})
describe('external worktree visibility policy', () => {
it('defaults undefined visibility to hide for new repos and show for legacy repos', () => {
expect(effectiveExternalWorktreeVisibility(makeRepo(), false)).toBe('hide')
expect(effectiveExternalWorktreeVisibility(makeRepo(), true)).toBe('show')
})
it('treats persisted repos without explicit visibility as legacy for upgrade safety', () => {
expect(isLegacyRepoForExternalWorktreeVisibility(makeRepo())).toBe(true)
})
it('computes legacy status from rollout timing, not from the stored visibility value', () => {
expect(
isLegacyRepoForExternalWorktreeVisibility(
makeRepo({
addedAt: EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT - 1,
externalWorktreeVisibility: 'hide'
})
)
).toBe(true)
})
it('honors an explicit legacy marker after visibility changes', () => {
expect(
isLegacyRepoForExternalWorktreeVisibility(
makeRepo({
externalWorktreeVisibility: 'hide',
externalWorktreeVisibilityLegacy: true
})
)
).toBe(true)
expect(
isLegacyRepoForExternalWorktreeVisibility(
makeRepo({
externalWorktreeVisibility: 'hide',
externalWorktreeVisibilityLegacy: false
})
)
).toBe(false)
})
it('treats repos without a valid addedAt as legacy for upgrade safety', () => {
expect(
isLegacyRepoForExternalWorktreeVisibility(makeRepo({ addedAt: undefined as never }))
).toBe(true)
expect(isLegacyRepoForExternalWorktreeVisibility(makeRepo({ addedAt: Number.NaN }))).toBe(true)
})
it('shows explicitly imported external worktrees while repo visibility stays hide', () => {
const repo = makeRepo({
externalWorktreeVisibility: 'hide',
importedExternalWorktreePaths: ['/scratch/imported']
})
expect(
shouldShowWorktree({
repo,
worktree: makeWorktree({ path: '/scratch/imported', isMainWorktree: false }),
ownership: 'external',
isLegacyRepoForVisibility: false,
isSelectedCheckout: false,
importedExternalWorktreePaths: repo.importedExternalWorktreePaths
})
).toBe(true)
expect(
shouldShowWorktree({
repo,
worktree: makeWorktree({ path: '/scratch/other', isMainWorktree: false }),
ownership: 'external',
isLegacyRepoForVisibility: false,
isSelectedCheckout: false,
importedExternalWorktreePaths: repo.importedExternalWorktreePaths
})
).toBe(false)
})
it('keeps unknown legacy rows visible for legacy repos after hiding external rows', () => {
const repo = makeRepo({
addedAt: EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT - 1,
externalWorktreeVisibility: 'hide'
})
expect(
shouldShowWorktree({
repo,
worktree: makeWorktree({ path: '/orca/workspaces/feature' }),
ownership: 'unknown-legacy',
isLegacyRepoForVisibility: true,
isSelectedCheckout: false
})
).toBe(true)
})
})
describe('agent scratch worktrees', () => {
const scratchPath = '/repos/app/.claude/worktrees/agent-a04ccaaa55ddadb91'
it('classifies sub-agent scratch paths as agent-scratch without metadata', () => {
const repo = makeRepo()
const settings = makeSettings()
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: scratchPath, isMainWorktree: false }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('agent-scratch')
})
it('classifies scratch worktrees created inside another linked checkout', () => {
const repo = makeRepo()
const settings = makeSettings()
const linkedCheckoutPath = '/orca/workspaces/app/feature-x'
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({
path: `${linkedCheckoutPath}/.claude/worktrees/agent-a04ccaaa`,
isMainWorktree: false
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo),
agentScratchWorktreePathMatcher: createAgentScratchWorktreePathMatcher([
repo.path,
linkedCheckoutPath
])
})
).toBe('agent-scratch')
})
it('keeps strong Orca metadata authoritative over the scratch path match', () => {
const repo = makeRepo()
const settings = makeSettings()
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({ path: scratchPath, isMainWorktree: false }),
meta: makeMeta({ orcaCreatedAt: 1 }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).toBe('orca-managed')
})
it('hides agent scratch even when the repo shows non-Orca worktrees', () => {
for (const repo of [
makeRepo({ externalWorktreeVisibility: 'show' }),
makeRepo({ addedAt: EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT - 1 })
]) {
const settings = makeSettings()
const detected = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({ path: scratchPath, isMainWorktree: false }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
expect(detected.ownership).toBe('agent-scratch')
expect(detected.visible).toBe(false)
}
})
it('still shows agent scratch for the selected checkout or an explicit import', () => {
const repo = makeRepo({
externalWorktreeVisibility: 'hide',
importedExternalWorktreePaths: [scratchPath]
})
expect(
shouldShowWorktree({
repo,
worktree: makeWorktree({ path: scratchPath, isMainWorktree: false }),
ownership: 'agent-scratch',
isLegacyRepoForVisibility: false,
isSelectedCheckout: false,
importedExternalWorktreePaths: repo.importedExternalWorktreePaths
})
).toBe(true)
expect(
shouldShowWorktree({
repo: makeRepo(),
worktree: makeWorktree({ path: scratchPath, isMainWorktree: false }),
ownership: 'agent-scratch',
isLegacyRepoForVisibility: false,
isSelectedCheckout: true
})
).toBe(true)
})
it('keeps agent scratch hidden in the metadata fallback while revealing the rest', () => {
const repo = makeRepo()
const settings = makeSettings()
const layouts = buildKnownOrcaWorkspaceLayouts(settings, repo)
const scratch = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({ path: scratchPath, isMainWorktree: false }),
knownOrcaLayouts: layouts
})
const external = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({ path: '/scratch/manual', isMainWorktree: false }),
knownOrcaLayouts: layouts
})
expect(applyMetadataFallbackVisibility(scratch)).toMatchObject({
visible: false,
ownership: 'agent-scratch'
})
expect(applyMetadataFallbackVisibility(external)).toMatchObject({
visible: true,
ownership: 'unknown-legacy'
})
})
it('preserves an explicit scratch import in the metadata fallback', () => {
const repo = makeRepo({
externalWorktreeVisibility: 'hide',
importedExternalWorktreePaths: [scratchPath]
})
const settings = makeSettings()
const scratch = toDetectedWorktree({
repo,
settings,
worktree: makeWorktree({ path: scratchPath, isMainWorktree: false }),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
expect(scratch).toMatchObject({ ownership: 'agent-scratch', visible: true })
expect(applyMetadataFallbackVisibility(scratch)).toBe(scratch)
})
it('does not classify worktrees from a repo stored below a scratch-looking parent', () => {
const repo = makeRepo({ path: '/repos/.claude/worktrees/app' })
const settings = makeSettings()
expect(
classifyWorktreeOwnership({
repo,
settings,
worktree: makeWorktree({
path: '/repos/.claude/worktrees/app/manual/feature-x',
isMainWorktree: false
}),
knownOrcaLayouts: buildKnownOrcaWorkspaceLayouts(settings, repo)
})
).not.toBe('agent-scratch')
})
})