feat: promote APFS shared paths (#8318)

This commit is contained in:
Neil 2026-07-11 14:24:39 -07:00 committed by GitHub
parent b18ec39e1f
commit 800967278e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 7 additions and 138 deletions

View File

@ -142,7 +142,6 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
experimentalActivity: true,
experimentalTerminalAttention: false,
compactWorktreeCards: false,
experimentalWorktreeSymlinks: false,
terminalWindowsShell: 'powershell.exe',
terminalWindowsPowerShellImplementation: 'powershell.exe',
enableGitHubAttribution: true,

View File

@ -147,7 +147,6 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
experimentalActivity: true,
experimentalTerminalAttention: false,
compactWorktreeCards: false,
experimentalWorktreeSymlinks: false,
terminalWindowsShell: 'powershell.exe',
terminalWindowsPowerShellImplementation: 'powershell.exe',
enableGitHubAttribution: true,

View File

@ -1903,8 +1903,8 @@ export async function createRemoteWorktree(
})
const workspaceLineage = recordWorkspaceLineageForCreatedWorktree(store, args, worktree, now)
// Why: `experimentalWorktreeSymlinks` is intentionally not wired up for
// remote (SSH) worktrees. Creating symlinks on the remote host would
// Why: shared paths are intentionally not wired up for remote (SSH)
// worktrees. Creating symlinks on the remote host would
// require a new relay method and authorization surface; the feature is
// local-only until that protocol work is in scope. Remote repos with
// `symlinkPaths` configured have them silently ignored here.
@ -2536,10 +2536,8 @@ export async function createLocalWorktree(
// Why: materialize user-configured paths from the primary checkout into the
// new worktree before any setup script runs, so scripts that reuse shared
// state (e.g. `node_modules`, `.env`) see those paths already in place.
// Gated on the experimental flag so disabling the feature globally skips
// the work even when a repo still has paths configured.
const symlinkPaths = repo.symlinkPaths ?? []
if (settings.experimentalWorktreeSymlinks && symlinkPaths.length > 0) {
if (symlinkPaths.length > 0) {
await timing.time('create_symlinks', async () => {
await createWorktreeLinkedPaths(repo.path, created.path, symlinkPaths)
})

View File

@ -853,7 +853,6 @@ type RuntimeStore = {
minimaxGroupId?: GlobalSettings['minimaxGroupId']
minimaxUsageModels?: GlobalSettings['minimaxUsageModels']
gitlabProjects?: GlobalSettings['gitlabProjects']
experimentalWorktreeSymlinks?: boolean
mobileAutoRestoreFitMs?: number | null
mobileEmulatorEnabled?: boolean
mobileEmulatorDefaultDeviceUdid?: string | null
@ -15142,11 +15141,7 @@ export class OrcaRuntimeService {
warnings: lineageWarnings
} = this.recordCreatedWorktreeLineage(worktree, lineageResolution)
if (
settings.experimentalWorktreeSymlinks &&
repo.symlinkPaths &&
repo.symlinkPaths.length > 0
) {
if (repo.symlinkPaths && repo.symlinkPaths.length > 0) {
await createWorktreeLinkedPaths(repo.path, created.path, repo.symlinkPaths)
}

View File

@ -43,9 +43,6 @@ export function ExperimentalPane({
const showTerminalAttention = matchesSettingsSearch(searchQuery, [
getExperimentalSearchEntry().terminalAttention
])
const showWorktreeSymlinks = matchesSettingsSearch(searchQuery, [
getExperimentalSearchEntry().symlinksOnWorktrees
])
const showAgentHibernation = matchesSettingsSearch(searchQuery, [
getExperimentalSearchEntry().agentHibernation
])
@ -322,57 +319,6 @@ export function ExperimentalPane({
</SearchableSetting>
) : null}
{showWorktreeSymlinks ? (
<SearchableSetting
title={translate(
'auto.components.settings.ExperimentalPane.24416f42cd',
'Shared paths on worktrees'
)}
description={translate(
'auto.components.settings.ExperimentalPane.fb82ea1d7a',
'Automatically materialize configured files or folders into newly created worktrees.'
)}
keywords={getExperimentalSearchEntry().symlinksOnWorktrees.keywords}
className="space-y-3 py-2"
>
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 shrink space-y-0.5">
<Label>
{translate(
'auto.components.settings.ExperimentalPane.24416f42cd',
'Shared paths on worktrees'
)}
</Label>
<p className="text-xs text-muted-foreground">
{translate(
'auto.components.settings.ExperimentalPane.9762364929',
'Uses APFS clone-copy on macOS when possible, otherwise symlinks configured folders or files into created worktrees.'
)}
</p>
</div>
<button
type="button"
role="switch"
aria-checked={settings.experimentalWorktreeSymlinks}
onClick={() =>
updateSettings({
experimentalWorktreeSymlinks: !settings.experimentalWorktreeSymlinks
})
}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors ${
settings.experimentalWorktreeSymlinks ? 'bg-foreground' : 'bg-muted-foreground/30'
}`}
>
<span
className={`inline-block h-3.5 w-3.5 transform rounded-full bg-background shadow-sm transition-transform ${
settings.experimentalWorktreeSymlinks ? 'translate-x-4' : 'translate-x-0.5'
}`}
/>
</button>
</div>
</SearchableSetting>
) : null}
<EphemeralVmsExperimentalSetting settings={settings} updateSettings={updateSettings} />
{hiddenExperimentalUnlocked ? <HiddenExperimentalGroup /> : null}

View File

@ -81,7 +81,6 @@ export function RepositoryPane({
const runtimeSessionSummary = useAppStore(
useShallow((state) => getProjectRuntimeSessionSummary(state, repo.id))
)
const symlinksEnabled = settings?.experimentalWorktreeSymlinks
const [confirmingRemove, setConfirmingRemove] = useState<string | null>(null)
const [copiedTemplate, setCopiedTemplate] = useState(false)
const copiedTemplateResetTimerRef = useRef<number | null>(null)
@ -353,7 +352,6 @@ export function RepositoryPane({
) : null,
!isFolder &&
!repo.connectionId &&
symlinksEnabled &&
(forceFullPaneForRepoMatch || matchesSettingsSearch(searchQuery, symlinkEntries)) ? (
<WorktreeSymlinksSection key="symlinks" repo={repo} updateRepo={updateRepo} />
) : null,

View File

@ -185,55 +185,6 @@ export const getExperimentalPaneSearchEntries = createLocalizedCatalog(
]
},
getNewWorktreeCardStyleSearchEntry(),
{
title: translate(
'auto.components.settings.experimental.search.78c2a8dc74',
'Shared paths on worktrees'
),
description: translate(
'auto.components.settings.experimental.search.603d29ed74',
'Automatically materialize configured files or folders into newly created worktrees using APFS clone-copy on macOS when possible, otherwise symlinks.'
),
keywords: [
...translateSearchKeyword(
'auto.components.settings.experimental.search.0d24759f14',
'experimental'
),
...translateSearchKeyword(
'auto.components.settings.experimental.search.d23ae13990',
'worktree'
),
...translateSearchKeyword(
'auto.components.settings.experimental.search.10b52f79c1',
'worktrees'
),
...translateSearchKeyword(
'auto.components.settings.experimental.search.c387565812',
'symlink'
),
...translateSearchKeyword(
'auto.components.settings.experimental.search.bff1ff7768',
'symlinks'
),
...translateSearchKeyword(
'auto.components.settings.experimental.search.3028f0bd3a',
'link'
),
...translateSearchKeyword(
'auto.components.settings.experimental.search.f082788cfe',
'links'
),
...translateSearchKeyword(
'auto.components.settings.experimental.search.3021571c30',
'shared'
),
...translateSearchKeyword('auto.components.settings.experimental.search.4ad605f222', 'env'),
...translateSearchKeyword(
'auto.components.settings.experimental.search.44c7f209d5',
'node_modules'
)
]
},
getEphemeralVmsSearchEntry()
]
)
@ -273,12 +224,6 @@ export function getExperimentalSearchEntry() {
'New card style'
)
),
symlinksOnWorktrees: findEntry(
translate(
'auto.components.settings.experimental.search.78c2a8dc74',
'Shared paths on worktrees'
)
),
ephemeralVms: findEntry(
translate('auto.components.settings.ephemeralVms.search.title', 'Per-Workspace Environments')
)

View File

@ -368,7 +368,6 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
experimentalNewWorktreeCardStyle: false,
experimentalEphemeralVms: false,
compactWorktreeCards: false,
experimentalWorktreeSymlinks: false,
// Why: local desktop remains the default server until the user explicitly
// selects a saved runtime environment.
activeRuntimeEnvironmentId: null,

View File

@ -298,7 +298,6 @@ export const SETTINGS_CHANGED_WHITELIST = [
'experimentalTerminalAttention',
'experimentalAgentHibernation',
'experimentalEphemeralVms',
'experimentalWorktreeSymlinks',
'geminiCliOAuthEnabled',
'openAgentTabsInChatByDefault'
] as const satisfies readonly BooleanGlobalSettingsKey[]

View File

@ -272,11 +272,9 @@ export type Repo = {
importedExternalWorktreePaths?: string[]
/** User permanently opted out of the new-external-worktree inbox for this repo. */
externalWorktreeDiscoverySuppressedAt?: number
/** Paths (relative to the primary checkout) that should be symlinked into
* newly created worktrees of this repo. Consumed only when the global
* `experimentalWorktreeSymlinks` flag is on the per-repo list is the
* "what to link", the global flag is the "whether to link at all" switch.
* Undefined/empty means no symlinks are created for this repo. */
/** Paths (relative to the primary checkout) that should be APFS clone-copied
* on macOS when possible, otherwise symlinked, into newly created worktrees.
* Undefined/empty means no shared paths are created for this repo. */
symlinkPaths?: string[]
/** Durable sidebar-only repo organization. Execution remains repo-scoped. */
projectGroupId?: string | null
@ -2922,13 +2920,6 @@ export type GlobalSettings = {
/** Legacy persisted key from the Experimental rollout. New writes use
* compactWorktreeCards. */
experimentalCompactWorktreeCards?: boolean
/** Experimental: when creating a worktree, automatically symlink a
* user-configured set of files/folders from the primary checkout (e.g.
* `.env`, `node_modules`) into the new worktree. Opt-in while the
* configuration surface and edge cases (conflicts with existing paths,
* cleanup on worktree delete) are still being worked out. */
experimentalWorktreeSymlinks: boolean
/** Active non-local runtime environment for client-routed RPC. `null`
* preserves the current local desktop behavior. */
activeRuntimeEnvironmentId?: string | null