diff --git a/src/main/ipc/worktree-logic.test.ts b/src/main/ipc/worktree-logic.test.ts index 219235ac9..a01fac836 100644 --- a/src/main/ipc/worktree-logic.test.ts +++ b/src/main/ipc/worktree-logic.test.ts @@ -242,13 +242,38 @@ describe('computeWorktreePath', () => { ).toBe('C:\\Projects\\app\\worktrees\\feature') }) - it('keeps legacy SSH sibling paths for global absolute workspace directories', () => { + it('qualifies SSH sibling paths with the repo name for global absolute workspace directories', () => { expect( - computeRemoteWorktreePath('feature', '/remote/repo', { + computeRemoteWorktreePath('main', '/remote/bioinformatist.github.io', { nestWorkspaces: false, workspaceDir: '/local/workspaces' }) - ).toBe('/remote/feature') + ).toBe('/remote/bioinformatist.github.io-main') + + expect( + computeRemoteWorktreePath('main-2', '/remote/dotfiles', { + nestWorkspaces: false, + workspaceDir: '/local/workspaces' + }) + ).toBe('/remote/dotfiles-main-2') + }) + + it('qualifies SSH sibling paths with the repo name on Windows remote paths', () => { + expect( + computeRemoteWorktreePath('main', 'C:\\Remote\\dotfiles', { + nestWorkspaces: false, + workspaceDir: 'C:\\Local\\workspaces' + }) + ).toBe('C:\\Remote\\dotfiles-main') + }) + + it('strips .git suffix from qualified SSH sibling paths', () => { + expect( + computeRemoteWorktreePath('main', '/remote/project.git', { + nestWorkspaces: false, + workspaceDir: '/local/workspaces' + }) + ).toBe('/remote/project-main') }) it('applies repo-specific SSH workspace directories on the remote path', () => { @@ -275,6 +300,20 @@ describe('computeWorktreePath', () => { ) ).toBe('C:\\Remote\\worktrees\\feature') }) + + it('keeps repo-specific absolute SSH workspace directories unqualified', () => { + expect( + computeRemoteWorktreePath( + 'feature', + '/remote/project/repo', + { + nestWorkspaces: false, + workspaceDir: '/remote/worktrees' + }, + { useConfiguredAbsolutePath: true } + ) + ).toBe('/remote/worktrees/feature') + }) }) describe('areWorktreePathsEqual', () => { diff --git a/src/main/ipc/worktree-logic.ts b/src/main/ipc/worktree-logic.ts index 1f52f2fff..16937b6b8 100644 --- a/src/main/ipc/worktree-logic.ts +++ b/src/main/ipc/worktree-logic.ts @@ -125,9 +125,10 @@ export function computeRemoteWorktreePath( return computeWorktreePath(sanitizedName, repoPath, settings) } // Why: absolute global workspaceDir values belong to the desktop machine. - // SSH worktrees keep the legacy repo-sibling root unless a repo-specific - // path opts into a remote-host location. - return getRuntimePathOps(repoPath, repoPath).join(repoPath, '..', sanitizedName) + // SSH falls back to repo-qualified sibling paths so origin/main is not shared. + const pathOps = getRuntimePathOps(repoPath, repoPath) + const repoName = pathOps.basename(repoPath).replace(/\.git$/, '') + return pathOps.join(repoPath, '..', `${repoName}-${sanitizedName}`) } export function getWorktreePathSettings( diff --git a/src/main/ipc/worktrees.test.ts b/src/main/ipc/worktrees.test.ts index 6e126be20..d61b92852 100644 --- a/src/main/ipc/worktrees.test.ts +++ b/src/main/ipc/worktrees.test.ts @@ -3210,7 +3210,7 @@ describe('registerWorktreeHandlers', () => { isMainWorktree: true }, { - path: '/remote/improve-dashboard', + path: '/remote/repo-improve-dashboard', head: 'abc123', branch: 'refs/heads/improve-dashboard', isBare: false, @@ -3250,7 +3250,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.listWorktrees).toHaveBeenCalledTimes(1) expect(provider.worktreeIsClean).not.toHaveBeenCalled() expect(store.setWorktreeMeta).toHaveBeenCalledWith( - 'repo-ssh::/remote/improve-dashboard', + 'repo-ssh::/remote/repo-improve-dashboard', expect.objectContaining({ linkedIssue: 123, linkedPR: 456, @@ -3308,7 +3308,7 @@ describe('registerWorktreeHandlers', () => { ]) .mockResolvedValueOnce([ { - path: '/remote/improve-dashboard', + path: '/remote/repo-improve-dashboard', head: 'abc123', branch: 'refs/heads/improve-dashboard', isBare: false, @@ -3405,7 +3405,7 @@ describe('registerWorktreeHandlers', () => { ]) .mockResolvedValueOnce([ { - path: '/remote/improve-dashboard', + path: '/remote/repo-improve-dashboard', head: 'abc123', branch: 'refs/heads/improve-dashboard', isBare: false, @@ -3512,7 +3512,7 @@ describe('registerWorktreeHandlers', () => { isMainWorktree: true }, { - path: '/remote/improve-dashboard', + path: '/remote/repo-improve-dashboard', head: 'abc123', branch: 'refs/heads/improve-dashboard', isBare: false, @@ -3623,7 +3623,7 @@ describe('registerWorktreeHandlers', () => { ]) .mockResolvedValueOnce([ { - path: '/remote/improve-dashboard', + path: '/remote/repo-improve-dashboard', head: 'abc123', branch: 'refs/heads/improve-dashboard', isBare: false, @@ -3675,7 +3675,7 @@ describe('registerWorktreeHandlers', () => { } if (args[0] === 'rev-parse' && args[1] === '--git-path') { return { - stdout: '/remote/repo/.git/worktrees/improve-dashboard/orca/setup-runner.sh\n', + stdout: '/remote/repo/.git/worktrees/repo-improve-dashboard/orca/setup-runner.sh\n', stderr: '' } } @@ -3688,7 +3688,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/improve-dashboard', + path: '/remote/repo-improve-dashboard', head: 'abc123', branch: 'refs/heads/improve-dashboard', isBare: false, @@ -3725,25 +3725,26 @@ describe('registerWorktreeHandlers', () => { }) expect(fsProvider.readFile).toHaveBeenCalledWith('/remote/repo/orca.yaml') - expect(fsProvider.readFile).toHaveBeenCalledWith('/remote/improve-dashboard/orca.yaml') + expect(fsProvider.readFile).toHaveBeenCalledWith('/remote/repo-improve-dashboard/orca.yaml') expect(provider.exec).toHaveBeenCalledWith( ['rev-parse', '--git-path', 'orca/setup-runner.sh'], - '/remote/improve-dashboard' + '/remote/repo-improve-dashboard' ) expect(fsProvider.createDir).toHaveBeenCalledWith( - '/remote/repo/.git/worktrees/improve-dashboard/orca' + '/remote/repo/.git/worktrees/repo-improve-dashboard/orca' ) expect(fsProvider.writeFile).toHaveBeenCalledWith( - '/remote/repo/.git/worktrees/improve-dashboard/orca/setup-runner.sh', + '/remote/repo/.git/worktrees/repo-improve-dashboard/orca/setup-runner.sh', '#!/usr/bin/env bash\nset -e\npnpm install\n' ) expect(result).toEqual( expect.objectContaining({ setup: { - runnerScriptPath: '/remote/repo/.git/worktrees/improve-dashboard/orca/setup-runner.sh', + runnerScriptPath: + '/remote/repo/.git/worktrees/repo-improve-dashboard/orca/setup-runner.sh', envVars: expect.objectContaining({ ORCA_ROOT_PATH: '/remote/repo', - ORCA_WORKTREE_PATH: '/remote/improve-dashboard' + ORCA_WORKTREE_PATH: '/remote/repo-improve-dashboard' }) } }) @@ -3772,7 +3773,7 @@ describe('registerWorktreeHandlers', () => { removeWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/sparse-dashboard', + path: '/remote/repo-sparse-dashboard', head: 'abc123', branch: 'refs/heads/sparse-dashboard', isBare: false, @@ -3813,23 +3814,23 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'sparse-dashboard', - '/remote/sparse-dashboard', + '/remote/repo-sparse-dashboard', { base: 'origin/main', noCheckout: true } ) expect(provider.exec).toHaveBeenCalledWith( ['sparse-checkout', 'init', '--cone'], - '/remote/sparse-dashboard' + '/remote/repo-sparse-dashboard' ) expect(provider.exec).toHaveBeenCalledWith( ['sparse-checkout', 'set', '--', 'apps/mobile', 'packages/shared'], - '/remote/sparse-dashboard' + '/remote/repo-sparse-dashboard' ) expect(provider.exec).toHaveBeenCalledWith( ['checkout', 'sparse-dashboard'], - '/remote/sparse-dashboard' + '/remote/repo-sparse-dashboard' ) expect(store.setWorktreeMeta).toHaveBeenCalledWith( - 'repo-ssh::/remote/sparse-dashboard', + 'repo-ssh::/remote/repo-sparse-dashboard', expect.objectContaining({ sparseDirectories: ['apps/mobile', 'packages/shared'], baseRef: 'refs/remotes/origin/main', @@ -3895,7 +3896,7 @@ describe('registerWorktreeHandlers', () => { ]) .mockResolvedValueOnce([ { - path: '/remote/fix-title-2', + path: '/remote/repo-fix-title-2', head: 'abc123', branch: 'refs/heads/feature/fix', isBare: false, @@ -3905,7 +3906,7 @@ describe('registerWorktreeHandlers', () => { } const fsProvider = { stat: vi.fn().mockImplementation(async (pathValue: string) => { - if (pathValue === '/remote/fix-title') { + if (pathValue === '/remote/repo-fix-title') { return { size: 0, type: 'directory', mtime: 0 } } const error = new Error('missing') as Error & { code: string } @@ -3934,11 +3935,11 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'feature/fix', - '/remote/fix-title-2', + '/remote/repo-fix-title-2', { checkoutExistingBranch: true } ) expect(mux.request).toHaveBeenCalledWith('session.registerRoot', { - rootPath: '/remote/fix-title-2' + rootPath: '/remote/repo-fix-title-2' }) }) @@ -3976,7 +3977,7 @@ describe('registerWorktreeHandlers', () => { removeWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/feature-something-2', + path: '/remote/repo-feature-something-2', head: 'abc123', branch: 'refs/heads/feature/something-2', isBare: false, @@ -4002,7 +4003,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'feature/something-2', - '/remote/feature-something-2', + '/remote/repo-feature-something-2', { base: 'origin/main' } ) }) @@ -4038,7 +4039,7 @@ describe('registerWorktreeHandlers', () => { removeWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/feature-something-2', + path: '/remote/repo-feature-something-2', head: 'abc123', branch: 'refs/heads/feature/something-2', isBare: false, @@ -4064,7 +4065,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'feature/something-2', - '/remote/feature-something-2', + '/remote/repo-feature-something-2', { base: 'origin/main' } ) }) @@ -4117,9 +4118,9 @@ describe('registerWorktreeHandlers', () => { expect(provider.exec).toHaveBeenCalledWith( ['config', '--local', '--unset-all', 'branch.sparse-dashboard.base'], - '/remote/sparse-dashboard' + '/remote/repo-sparse-dashboard' ) - expect(provider.removeWorktree).toHaveBeenCalledWith('/remote/sparse-dashboard', true, { + expect(provider.removeWorktree).toHaveBeenCalledWith('/remote/repo-sparse-dashboard', true, { deleteBranch: true, forceBranchDelete: true }) @@ -4210,7 +4211,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValueOnce([ { - path: '/remote/improve-dashboard', + path: '/remote/repo-improve-dashboard', head: 'abc123', branch: 'refs/heads/improve-dashboard', isBare: false, @@ -4244,7 +4245,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'improve-dashboard', - '/remote/improve-dashboard', + '/remote/repo-improve-dashboard', { base: 'origin/main' } @@ -4282,7 +4283,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/local-branch-base', + path: '/remote/repo-local-branch-base', head: 'develop-sha', branch: 'refs/heads/local-branch-base', isBare: false, @@ -4316,7 +4317,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'local-branch-base', - '/remote/local-branch-base', + '/remote/repo-local-branch-base', { base: 'develop' } @@ -4360,7 +4361,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/slash-local-base', + path: '/remote/repo-slash-local-base', head: 'team-feature-sha', branch: 'refs/heads/slash-local-base', isBare: false, @@ -4398,7 +4399,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'slash-local-base', - '/remote/slash-local-base', + '/remote/repo-slash-local-base', { base: 'team/feature' } @@ -4428,7 +4429,7 @@ describe('registerWorktreeHandlers', () => { .fn() .mockResolvedValueOnce([ { - path: '/remote/first-worktree', + path: '/remote/repo-first-worktree', head: 'abc123', branch: 'refs/heads/first-worktree', isBare: false, @@ -4437,7 +4438,7 @@ describe('registerWorktreeHandlers', () => { ]) .mockResolvedValueOnce([ { - path: '/remote/second-worktree', + path: '/remote/repo-second-worktree', head: 'def456', branch: 'refs/heads/second-worktree', isBare: false, @@ -4506,7 +4507,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/fix-title', + path: '/remote/repo-fix-title', head: sha, branch: 'refs/heads/feature/fix', isBare: false, @@ -4536,7 +4537,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'feature/fix', - '/remote/fix-title', + '/remote/repo-fix-title', { base: sha } ) }) @@ -4566,7 +4567,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/prefetched-worktree', + path: '/remote/repo-prefetched-worktree', head: 'abc123', branch: 'refs/heads/prefetched-worktree', isBare: false, @@ -4642,7 +4643,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/prefetched-worktree', + path: '/remote/repo-prefetched-worktree', head: 'abc123', branch: 'refs/heads/prefetched-worktree', isBare: false, @@ -4678,7 +4679,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'prefetched-worktree', - '/remote/prefetched-worktree', + '/remote/repo-prefetched-worktree', { base: 'origin/main' } @@ -4718,7 +4719,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/slash-local-base', + path: '/remote/repo-slash-local-base', head: 'team-feature-sha', branch: 'refs/heads/slash-local-base', isBare: false, @@ -4753,7 +4754,7 @@ describe('registerWorktreeHandlers', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'slash-local-base', - '/remote/slash-local-base', + '/remote/repo-slash-local-base', { base: 'team/feature' } @@ -4785,7 +4786,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/prefetched-worktree', + path: '/remote/repo-prefetched-worktree', head: 'abc123', branch: 'refs/heads/prefetched-worktree', isBare: false, @@ -4851,7 +4852,7 @@ describe('registerWorktreeHandlers', () => { addWorktree: vi.fn().mockResolvedValue(undefined), listWorktrees: vi.fn().mockResolvedValue([ { - path: '/remote/local-base-worktree', + path: '/remote/repo-local-base-worktree', head: 'abc123', branch: 'refs/heads/local-base-worktree', isBare: false, diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index 59d2ff338..237a2b1f3 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -4677,7 +4677,7 @@ describe('OrcaRuntimeService', () => { vi.mocked(listWorktrees).mockClear() vi.mocked(addWorktree).mockClear() const created = { - path: '/remote/mobile-feature', + path: '/remote/repo-mobile-feature', head: 'def', branch: 'refs/heads/mobile-feature', isBare: false, @@ -4740,7 +4740,7 @@ describe('OrcaRuntimeService', () => { expect(provider.addWorktree).toHaveBeenCalledWith( '/remote/repo', 'mobile-feature', - '/remote/mobile-feature', + '/remote/repo-mobile-feature', { base: 'origin/main' } ) expect(result.worktree).toMatchObject({