mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(worktrees): expand a leading ~ in the worktree location (#35032)
A workspaceDir of "~/..." was never expanded, so it resolved relative to each repo and worktrees were created under <repo>/~. Expand a leading ~ when settings are loaded and on every settings write, and share the expansion with the floating-workspace path resolver. Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { join, sep } from 'node:path'
|
||||
|
||||
export function expandLeadingHomePath(input: string, home: string): string {
|
||||
if (input === '~') {
|
||||
return home
|
||||
}
|
||||
if (input.startsWith(`~${sep}`) || (process.platform === 'win32' && input.startsWith('~/'))) {
|
||||
return join(home, input.slice(2))
|
||||
}
|
||||
return input
|
||||
}
|
||||
@@ -5,29 +5,15 @@ import { getAppEnvironment } from '../../shared/app-environment'
|
||||
import type { GlobalSettings } from '../../shared/global-settings-types'
|
||||
import type { FloatingTerminalCwdRequest } from '../../shared/ui-chrome-types'
|
||||
import type { Store } from '../persistence'
|
||||
import { resolveFloatingWorkspacePath } from '../persistence/restoring-sessions/floating-workspace-normalization'
|
||||
|
||||
type FloatingTerminalSettings = Pick<GlobalSettings, 'floatingTerminalTrustedCwds'>
|
||||
|
||||
const FLOATING_WORKSPACE_DIRNAME = 'floating-workspace'
|
||||
|
||||
function expandHomePath(input: string, home: string): string {
|
||||
if (input === '~') {
|
||||
return home
|
||||
}
|
||||
if (input.startsWith(`~${path.sep}`)) {
|
||||
return path.join(home, input.slice(2))
|
||||
}
|
||||
if (process.platform === 'win32' && input.startsWith('~/')) {
|
||||
return path.join(home, input.slice(2))
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
function resolveFloatingWorkspaceInput(input: string): string {
|
||||
// Headless runtime uses this path resolver without an Electron app.
|
||||
const home = getAppEnvironment().getPath('home')
|
||||
const expanded = expandHomePath(input, home)
|
||||
return path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(home, expanded)
|
||||
return resolveFloatingWorkspacePath(input, getAppEnvironment().getPath('home'))
|
||||
}
|
||||
|
||||
async function canonicalizeAccessibleDirectory(dirPath: string): Promise<string | null> {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { makeTerminalTab } from '../persistence-session-fixtures'
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { writeFileSync, rmSync, mkdtempSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { homedir, tmpdir } from 'node:os'
|
||||
import type { GlobalSettings } from '../../../shared/global-settings-types'
|
||||
import type { PersistedState } from '../../../shared/persisted-state-types'
|
||||
import { getDefaultWorkspaceSession } from '../../../shared/constants'
|
||||
@@ -112,6 +112,18 @@ describe('Store', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('expands a leading ~ in the worktree location on load and every settings write', async () => {
|
||||
writeDataFile({ settings: { workspaceDir: '~/source/.orca/worktrees' } })
|
||||
const store = await createStore()
|
||||
|
||||
expect(store.getSettings().workspaceDir).toBe(join(homedir(), 'source/.orca/worktrees'))
|
||||
expect(store.updateSettings({ workspaceDir: '~' }).workspaceDir).toBe(homedir())
|
||||
expect(store.updateSettings({ workspaceDir: '~/wt' }).workspaceDir).toBe(join(homedir(), 'wt'))
|
||||
// Repo-relative locations and other names that start with ~ stay as written.
|
||||
expect(store.updateSettings({ workspaceDir: '.worktrees' }).workspaceDir).toBe('.worktrees')
|
||||
expect(store.updateSettings({ workspaceDir: '~wt' }).workspaceDir).toBe('~wt')
|
||||
})
|
||||
|
||||
it('normalizes bot-author overrides on load and every settings write', async () => {
|
||||
writeDataFile({
|
||||
settings: {
|
||||
|
||||
@@ -42,6 +42,7 @@ import { normalizeNotificationSettings } from './onboarding-normalization'
|
||||
import { retireLegacyInstructionsForClearedTextActionRecipes } from './source-control-settings'
|
||||
import {
|
||||
buildWorkspaceDirHistoryForUpdate,
|
||||
expandWorkspaceDirHome,
|
||||
stripRetiredGlobalSettings
|
||||
} from './terminal-settings-migrations'
|
||||
|
||||
@@ -58,7 +59,7 @@ export function updateSettings(
|
||||
updates: Partial<GlobalSettings>,
|
||||
options: { notifyListeners?: boolean; originWebContentsId?: number } = {}
|
||||
): GlobalSettings {
|
||||
const sanitizedUpdates = stripRetiredGlobalSettings(updates)
|
||||
const sanitizedUpdates = expandWorkspaceDirHome(stripRetiredGlobalSettings(updates))
|
||||
const linkBehavior = updates.terminalLinkClickBehavior
|
||||
if (linkBehavior === 'actions' || linkBehavior === 'open' || linkBehavior === 'none') {
|
||||
// Loaded defaults are ambiguous; synchronize the legacy switch only on an explicit write.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { homedir } from 'node:os'
|
||||
import type { GlobalSettings, OrcaWorkspaceLayout } from '../../../shared/global-settings-types'
|
||||
import { normalizeRuntimePathForComparison } from '../../../shared/cross-platform-path'
|
||||
import {
|
||||
@@ -10,6 +11,14 @@ import {
|
||||
normalizeTuiAgentArgsRecord,
|
||||
normalizeTuiAgentEnvRecord
|
||||
} from '../../../shared/tui-agent-launch-defaults'
|
||||
import { expandLeadingHomePath } from '../../home-path-expansion'
|
||||
|
||||
/** Unexpanded, a leading ~ resolves inside each repo and worktrees land in <repo>/~ (#34944). */
|
||||
export function expandWorkspaceDirHome(settings: Partial<GlobalSettings>): Partial<GlobalSettings> {
|
||||
return typeof settings.workspaceDir === 'string'
|
||||
? { ...settings, workspaceDir: expandLeadingHomePath(settings.workspaceDir, homedir()) }
|
||||
: settings
|
||||
}
|
||||
|
||||
export function buildWorkspaceDirHistoryForUpdate(
|
||||
current: GlobalSettings,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { projectSourceControlAiToLegacyCommitMessageAi } from '../../../shared/s
|
||||
import { normalizeNativeChatShellEnvironmentVariables } from '../../../shared/native-chat/native-chat-shell-environment'
|
||||
import { publishedLegacyChatPermissionMode } from '../../../shared/agent-chat-permission-mode'
|
||||
import {
|
||||
expandWorkspaceDirHome,
|
||||
savedChatUiWithTerminalDefaultView,
|
||||
stripRetiredGlobalSettings
|
||||
} from '../applying-settings/terminal-settings-migrations'
|
||||
@@ -64,7 +65,7 @@ export function normalizeLoadedGlobalSettings(
|
||||
return {
|
||||
...defaults.settings,
|
||||
// Legacy stored booleans cannot distinguish opt-in from the old default; retain explicit true.
|
||||
...stripRetiredGlobalSettings(parsed.settings),
|
||||
...expandWorkspaceDirHome(stripRetiredGlobalSettings(parsed.settings)),
|
||||
// Preserve terminal-first new tabs when retiring the old default-view switch.
|
||||
...(savedChatUiWithTerminalDefaultView(parsed.settings)
|
||||
? { experimentalNativeChat: false }
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import { realpathSync, statSync } from 'node:fs'
|
||||
import { isAbsolute, join, resolve, sep } from 'node:path'
|
||||
|
||||
export function expandFloatingWorkspaceHomePath(input: string, home: string): string {
|
||||
if (input === '~') {
|
||||
return home
|
||||
}
|
||||
if (input.startsWith(`~${sep}`) || (process.platform === 'win32' && input.startsWith('~/'))) {
|
||||
return join(home, input.slice(2))
|
||||
}
|
||||
return input
|
||||
}
|
||||
import { isAbsolute, resolve } from 'node:path'
|
||||
import { expandLeadingHomePath } from '../../home-path-expansion'
|
||||
|
||||
export function resolveFloatingWorkspacePath(input: string, home: string): string {
|
||||
const expanded = expandFloatingWorkspaceHomePath(input, home)
|
||||
const expanded = expandLeadingHomePath(input, home)
|
||||
return isAbsolute(expanded) ? resolve(expanded) : resolve(home, expanded)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user