feat: promote APFS shared paths (#8318)
This commit is contained in:
parent
b18ec39e1f
commit
800967278e
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -298,7 +298,6 @@ export const SETTINGS_CHANGED_WHITELIST = [
|
|||
'experimentalTerminalAttention',
|
||||
'experimentalAgentHibernation',
|
||||
'experimentalEphemeralVms',
|
||||
'experimentalWorktreeSymlinks',
|
||||
'geminiCliOAuthEnabled',
|
||||
'openAgentTabsInChatByDefault'
|
||||
] as const satisfies readonly BooleanGlobalSettingsKey[]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue