mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
Merge remote-tracking branch 'origin/main' into brennanb2025/global-worktree-visibility-defaults
# Conflicts: # src/main/ipc/worktrees.ts # src/main/persistence.ts # src/preload/api-types.ts # src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx # src/shared/types.ts # src/shared/worktree/ownership.test.ts # src/shared/worktree/ownership.ts # src/shared/worktree/visibility-sources.test.ts
This commit is contained in:
@@ -167,7 +167,6 @@ inline src/main/window/attach-main-window-services.ts
|
||||
inline src/main/window/createMainWindow.test.ts
|
||||
inline src/main/window/createMainWindow.ts
|
||||
inline src/main/workspace-space-analysis.ts
|
||||
inline src/preload/api-types.ts
|
||||
inline src/preload/index.ts
|
||||
inline src/relay/agent-hook-server.ts
|
||||
inline src/relay/dispatcher.ts
|
||||
@@ -318,7 +317,7 @@ inline src/shared/agent-hook-listener.ts
|
||||
inline src/shared/automation-schedules.ts
|
||||
inline src/shared/commit-message-agent-spec.ts
|
||||
inline src/shared/constants.ts
|
||||
inline src/shared/github-project-types.ts
|
||||
inline src/shared/github/project-types.ts
|
||||
inline src/shared/keybindings.test.ts
|
||||
inline src/shared/keybindings.ts
|
||||
inline src/shared/marine-creatures.ts
|
||||
@@ -327,7 +326,6 @@ inline src/shared/runtime-types.ts
|
||||
inline src/shared/source-control-ai.ts
|
||||
inline src/shared/telemetry-events.ts
|
||||
inline src/shared/text-search.ts
|
||||
inline src/shared/types.ts
|
||||
inline tests/e2e/helpers/terminal.ts
|
||||
inline tests/e2e/terminal-panes.spec.ts
|
||||
mobile-config app/h/*/files/*.tsx
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { planWorktreeSortOrderUpdates } from '../../src/shared/worktree-sort-order-update.ts'
|
||||
import { planWorktreeSortOrderUpdates } from '../../src/shared/worktree/sort-order-update.ts'
|
||||
import { reuseEqualCatalogRows } from '../../src/renderer/src/store/slices/worktree-catalog-reconciliation.ts'
|
||||
|
||||
const WORKTREE_COUNT = 655
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"../src/renderer/src/**/*",
|
||||
"../src/renderer/src/**/*.tsx",
|
||||
"../src/preload/api-types.ts",
|
||||
"../src/preload/api/**/*",
|
||||
"../src/shared/**/*",
|
||||
"../src/main/gitlab/mappers.ts",
|
||||
"../src/main/ipc/worktree-branch-name.ts",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"../src/renderer/src/**/*",
|
||||
"../src/renderer/src/**/*.tsx",
|
||||
"../src/preload/api-types.ts",
|
||||
"../src/preload/api/**/*",
|
||||
"../src/shared/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
|
||||
@@ -119,11 +119,11 @@ import {
|
||||
isIterationCurrent,
|
||||
sortRows,
|
||||
type ProjectGroup
|
||||
} from '../../../../src/shared/github-project-group-sort'
|
||||
} from '../../../../src/shared/github/project-group-sort'
|
||||
import type {
|
||||
GitHubProjectSortDirection,
|
||||
GitHubProjectTable as SharedGitHubProjectTable
|
||||
} from '../../../../src/shared/github-project-types'
|
||||
} from '../../../../src/shared/github/project-types'
|
||||
import {
|
||||
CROSS_REPO_DISPLAY_LIMIT,
|
||||
isGitHubWorkItemsSshRemoteRequiredError,
|
||||
@@ -161,7 +161,7 @@ import type { HostedReviewDecision } from '../../../../src/shared/hosted-review'
|
||||
import {
|
||||
githubProjectHost,
|
||||
githubProjectIdentityKey as githubProjectKey
|
||||
} from '../../../../src/shared/github-project-identity'
|
||||
} from '../../../../src/shared/github/project-identity'
|
||||
|
||||
type RepoSummary = {
|
||||
id: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isPathInsideOrEqual, isRuntimePathAbsolute } from '../../../src/shared/cross-platform-path'
|
||||
import { parseWslUncPath } from '../../../src/shared/wsl-paths'
|
||||
import { splitWorktreeIdForFilesystem } from '../../../src/shared/worktree-id'
|
||||
import { splitWorktreeIdForFilesystem } from '../../../src/shared/worktree/id'
|
||||
import type { AiVaultSession } from '../../../src/shared/ai-vault-types'
|
||||
import type { Worktree } from '../worktree/workspace-list-types'
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { HostedReviewInfo } from '../../../src/shared/hosted-review'
|
||||
import {
|
||||
normalizeGitHubPRForBranchOutcome,
|
||||
type GitHubPRForBranchResponse
|
||||
} from '../../../src/shared/github-pr-for-branch-outcome'
|
||||
} from '../../../src/shared/github/pull-request-for-branch-outcome'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import type { RpcSuccess } from '../transport/types'
|
||||
import { mobileRepoSelectorFromWorktreeId } from '../source-control/mobile-pr-create'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GitHubPRMergeMethod, PRState } from '../../../src/shared/types'
|
||||
import { githubRepoIdentityKey } from '../../../src/shared/github-repository-identity-key'
|
||||
import { githubRepoIdentityKey } from '../../../src/shared/github/repository-identity-key'
|
||||
import { classifyPrSidebarFailure } from './mobile-pr-sidebar-state'
|
||||
import { createOptimisticField, type OptimisticField } from './optimistic-write-sequence'
|
||||
import type { GitHubPrMutationOutcome } from './github-pr-mutations'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useFocusEffect } from 'expo-router'
|
||||
import type { RuntimeClientEventStreamMessage } from '../../../src/shared/runtime-client-events'
|
||||
import { getRepoIdFromWorktreeId } from '../../../src/shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../../../src/shared/worktree/id'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import type { ConnectionState, RpcSuccess } from '../transport/types'
|
||||
import { getLiveWorktreeDisplayName, type WorktreeDisplayNameSource } from './worktree-display-name'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GitHubProjectIdentity } from '../../../src/shared/github-project-identity'
|
||||
import type { GitHubProjectIdentity } from '../../../src/shared/github/project-identity'
|
||||
|
||||
export type GitHubProjectOwnerType = GitHubProjectIdentity['ownerType']
|
||||
export type GitHubProjectRef = GitHubProjectIdentity
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
githubRepoIdentityKey,
|
||||
isDefaultGitHubHost
|
||||
} from '../../../src/shared/github-repository-identity-key'
|
||||
} from '../../../src/shared/github/repository-identity-key'
|
||||
|
||||
export type GitHubProjectRepoMatch = {
|
||||
id: string
|
||||
|
||||
@@ -9,7 +9,7 @@ import { parseGitLabIssueOrMRLink } from '../../../src/shared/new-workspace/gitl
|
||||
import { isSmartWorkspaceSourceQueryWithinLimit } from '../../../src/shared/new-workspace/smart-workspace-source-results'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import type { RpcSuccess } from '../transport/types'
|
||||
import { githubRepoIdentityKey } from '../../../src/shared/github-repository-identity-key'
|
||||
import { githubRepoIdentityKey } from '../../../src/shared/github/repository-identity-key'
|
||||
|
||||
// A repo the picker can switch to for a cross-repo GitHub paste. Slug is derived
|
||||
// best-effort from the repo's remote metadata.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {
|
||||
LinearMcpIssueListRequest,
|
||||
LinearMcpIssueListResult
|
||||
} from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import type { CommandHandler } from '../dispatch'
|
||||
import {
|
||||
getOptionalNonNegativeIntegerFlag,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
LinearIssueRelationship,
|
||||
LinearIssueRelationWriteRequest,
|
||||
LinearIssueRelationWriteResult
|
||||
} from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import type { CommandHandler } from '../dispatch'
|
||||
import { getRequiredStringFlag } from '../flags'
|
||||
import { printResult } from '../format'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {
|
||||
LinearSaveIssueRequest,
|
||||
LinearSaveIssueResult
|
||||
} from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import type { CommandHandler } from '../dispatch'
|
||||
import { printResult } from '../format'
|
||||
import { formatLinearSaveIssue } from '../linear-format'
|
||||
|
||||
@@ -19,8 +19,8 @@ import type {
|
||||
LinearTeamListResult,
|
||||
LinearTeamMembersResult,
|
||||
LinearTeamStatesResult
|
||||
} from '../../shared/linear-agent-access'
|
||||
import { clampLinearSearchLimit } from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import { clampLinearSearchLimit } from '../../shared/linear/agent-access'
|
||||
import type { CommandHandler } from '../dispatch'
|
||||
import { printResult } from '../format'
|
||||
import { RuntimeClientError } from '../runtime-client'
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
buildLinearIssueLinkUpdates,
|
||||
LINEAR_ISSUE_LINK_CLEARED,
|
||||
type LinearIssueLinkUpdates
|
||||
} from '../../shared/linear-links'
|
||||
} from '../../shared/linear/links'
|
||||
import { RuntimeClientError } from '../runtime-client'
|
||||
|
||||
export function getOptionalLinearIssueLinkFlag(
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
LinearMcpIssueListResult,
|
||||
LinearProjectListResult,
|
||||
LinearSearchResult
|
||||
} from '../shared/linear-agent-access'
|
||||
} from '../shared/linear/agent-access'
|
||||
import {
|
||||
formatLinearCreate,
|
||||
formatLinearIssue,
|
||||
|
||||
@@ -16,11 +16,11 @@ import type {
|
||||
LinearTeamMembersResult,
|
||||
LinearTeamStatesResult,
|
||||
LinearStatusSetResult
|
||||
} from '../shared/linear-agent-access'
|
||||
} from '../shared/linear/agent-access'
|
||||
import {
|
||||
formatLinearProjectListRows,
|
||||
linearProjectListWarningLines
|
||||
} from '../shared/linear-project-list-format'
|
||||
} from '../shared/linear/project-list-format'
|
||||
|
||||
export function formatLinearIssue(result: LinearIssueContextResult): string {
|
||||
const issue = result.issue
|
||||
|
||||
@@ -6,13 +6,13 @@ import type {
|
||||
LinearIssueRequest,
|
||||
LinearIssueTaskUpdateRequest,
|
||||
LinearWriteTargetRequest
|
||||
} from '../shared/linear-agent-access'
|
||||
} from '../shared/linear/agent-access'
|
||||
import {
|
||||
LINEAR_CHILDREN_MAX_DEPTH,
|
||||
LINEAR_WRITE_BODY_CAP,
|
||||
clampLinearIssueDepth
|
||||
} from '../shared/linear-agent-access'
|
||||
import { isLinearUuid } from '../shared/linear-uuid'
|
||||
} from '../shared/linear/agent-access'
|
||||
import { isLinearUuid } from '../shared/linear/uuid'
|
||||
import {
|
||||
getOptionalNonNegativeIntegerFlag,
|
||||
getOptionalStringFlag,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LinearSaveIssueRequest } from '../shared/linear-agent-access'
|
||||
import type { LinearSaveIssueRequest } from '../shared/linear/agent-access'
|
||||
import {
|
||||
getOptionalNullableNumberFlag,
|
||||
getOptionalStringFlag,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { vi } from 'vitest'
|
||||
import type { GlobalSettings, Repo } from '../../shared/types'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree-id'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree/id'
|
||||
import type {
|
||||
FirstWorkBranchRenameDeps,
|
||||
FirstWorkBranchRenameEvent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { GlobalSettings, Repo } from '../../shared/types'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree-id'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree/id'
|
||||
|
||||
const {
|
||||
gitExecFileAsyncMock,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// On first agent work in a fresh workspace, replace the auto-generated creature branch (e.g. `you/Nautilus`) with a short work-derived name.
|
||||
import type { GlobalSettings, Repo } from '../../shared/types'
|
||||
import { getRepoIdFromWorktreeId, splitWorktreeIdForFilesystem } from '../../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId, splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
|
||||
import { parseWorkspaceKey } from '../../shared/workspace-scope'
|
||||
import { parsePaneKey } from '../../shared/stable-pane-id'
|
||||
import {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
FOLDER_WORKSPACE_INSTANCE_SEPARATOR,
|
||||
getRepoIdFromWorktreeId,
|
||||
splitWorktreeIdForFilesystem
|
||||
} from '../../shared/worktree-id'
|
||||
} from '../../shared/worktree/id'
|
||||
import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
|
||||
import { planWorktreeFolderRename } from '../ipc/worktree-folder-rename-target'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type ExecutionHostId
|
||||
} from '../../shared/execution-host'
|
||||
import type { FolderWorkspace, ProjectGroup } from '../../shared/types'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree/id'
|
||||
import { parseWorkspaceKey } from '../../shared/workspace-scope'
|
||||
|
||||
export type RestoredSubagentLivenessSweepDeps = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Automation } from '../../shared/automations-types'
|
||||
import { getAutomationLegacyRepoId } from '../../shared/automation-run-identity'
|
||||
import { getRepoExecutionHostId, parseExecutionHostId } from '../../shared/execution-host'
|
||||
import type { ProjectHostSetup, Repo } from '../../shared/types'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree-id'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
|
||||
|
||||
export type AutomationRunTargetResult =
|
||||
| { ok: true; cwd: string; repo: Repo; setup?: ProjectHostSetup }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree-id'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
|
||||
import { isWslShellName } from '../../shared/local-windows-terminal-runtime'
|
||||
import { getDefaultWslDistro, parseWslPath } from '../wsl'
|
||||
import { parsePtySessionId } from './pty-session-id'
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
removeSafeUntrackedDiscardTargets
|
||||
} from '../../shared/git-discard-path-safety'
|
||||
import { readBranchCompareHead } from '../../shared/git-branch-compare-head'
|
||||
import { resolveWorktreeAddBaseRef } from '../../shared/worktree-base-ref'
|
||||
import { resolveWorktreeAddBaseRef } from '../../shared/worktree/base-ref'
|
||||
import { resolveWorktreeBaseCommitOid } from './worktree-base-ref-probe'
|
||||
import { getLargeDiffRenderLimit } from '../../shared/large-diff-render-limit'
|
||||
import { InFlightPromiseDedupe, stableInFlightKey } from '../../shared/in-flight-promise-dedupe'
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
branchHasNoUnmergedChangesWithLazyTargetRefresh,
|
||||
getBranchCleanupTargetRefs
|
||||
} from '../../shared/git-branch-cleanup'
|
||||
import { resolveWorktreeAddBaseRef } from '../../shared/worktree-base-ref'
|
||||
import { resolveWorktreeAddBaseRef } from '../../shared/worktree/base-ref'
|
||||
import { withSpan } from '../observability/tracer'
|
||||
import { withWorktreeRemoveStageSpan } from '../observability/instrumentation'
|
||||
import {
|
||||
@@ -20,8 +20,8 @@ import type {
|
||||
LocalBaseRefUpdateSuggestion,
|
||||
RemoveWorktreeResult
|
||||
} from '../../shared/types'
|
||||
import { assertWorktreeUnlockedForRemoval } from '../../shared/worktree-removal'
|
||||
import { isSubmoduleWorktreeRemovalRefusal } from '../../shared/worktree-submodule-removal'
|
||||
import { assertWorktreeUnlockedForRemoval } from '../../shared/worktree/removal'
|
||||
import { isSubmoduleWorktreeRemovalRefusal } from '../../shared/worktree/submodule-removal'
|
||||
import { decodeGitCQuotedPath } from '../../shared/git-cquoted-path'
|
||||
import { parseGitRevListAheadBehindCounts } from '../../shared/git-rev-list-output'
|
||||
import { parseWslUncPath } from '../../shared/wsl-paths'
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* uses stable field labels ("Token scopes:", "(GITHUB_TOKEN)", etc.).
|
||||
*/
|
||||
import { ghExecFileAsync } from '../git/runner'
|
||||
import type { GhAuthDiagnostic, GhAuthAccount } from '../../shared/github-auth-types'
|
||||
import type { GhAuthDiagnostic, GhAuthAccount } from '../../shared/github/auth-types'
|
||||
|
||||
// Required scopes for ProjectV2 GraphQL access in Orca. `project` is the
|
||||
// scope that gates ProjectV2 reads/writes; the others are needed for the
|
||||
|
||||
@@ -118,7 +118,7 @@ import {
|
||||
_resetMergeQueueCacheForTests,
|
||||
_resetOwnerRepoCache
|
||||
} from './client'
|
||||
import { GITHUB_WORK_ITEMS_QUERY_MAX_BYTES } from '../../shared/github-work-items-query-bounds'
|
||||
import { GITHUB_WORK_ITEMS_QUERY_MAX_BYTES } from '../../shared/github/work-items-query-bounds'
|
||||
|
||||
import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository'
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ import {
|
||||
_resetMergeQueueCacheForTests,
|
||||
_resetOwnerRepoCache
|
||||
} from './client'
|
||||
import { GITHUB_WORK_ITEMS_QUERY_MAX_BYTES } from '../../shared/github-work-items-query-bounds'
|
||||
import { GITHUB_WORK_ITEMS_QUERY_MAX_BYTES } from '../../shared/github/work-items-query-bounds'
|
||||
|
||||
import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository'
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ import {
|
||||
normalizeHostedReviewBaseRef,
|
||||
normalizeHostedReviewHeadRef
|
||||
} from '../../shared/hosted-review-refs'
|
||||
import { normalizeGitHubPRMergeMethodSettings } from '../../shared/github-pr-merge-methods'
|
||||
import { normalizeGitHubPRMergeMethodSettings } from '../../shared/github/pull-request-merge-methods'
|
||||
import { summarizeProviderChecks } from '../../shared/provider-check-summary'
|
||||
import { isGitHubWorkItemsQueryTooLarge } from '../../shared/github-work-items-query-bounds'
|
||||
import { classifyGitHubUnavailable } from '../../shared/github-api-availability'
|
||||
import { isGitHubWorkItemsQueryTooLarge } from '../../shared/github/work-items-query-bounds'
|
||||
import { classifyGitHubUnavailable } from '../../shared/github/api-availability'
|
||||
import { parseTaskQuery, type ParsedTaskQuery } from '../../shared/task-query'
|
||||
import {
|
||||
GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE,
|
||||
@@ -91,7 +91,7 @@ import {
|
||||
type GitHubRepoExecOptions,
|
||||
type GitHubApiRepository
|
||||
} from './github-api-repository'
|
||||
import { githubRepoIdentityKey } from '../../shared/github-repository-identity-key'
|
||||
import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key'
|
||||
export { _resetOwnerRepoCache } from './gh-utils'
|
||||
export {
|
||||
getIssue,
|
||||
@@ -127,7 +127,7 @@ import {
|
||||
import {
|
||||
GITHUB_CHECK_DETAILS_HOST_TIMEOUT_MS,
|
||||
GITHUB_CHECK_DETAILS_TIMEOUT_MESSAGE
|
||||
} from '../../shared/github-check-details-deadline'
|
||||
} from '../../shared/github/check-details-deadline'
|
||||
import { hydrateGitHubPRStack, mergeGitHubPRStack } from './github-pr-stack'
|
||||
|
||||
type GhExecOptions = GitHubRepoExecOptions & { signal?: AbortSignal }
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
__resetLocalGitConfigSignatureCacheForTests,
|
||||
readLocalGitConfigSignature
|
||||
} from './local-git-config-signature'
|
||||
import { GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN } from '../../shared/github-work-items-query-bounds'
|
||||
import { GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN } from '../../shared/github/work-items-query-bounds'
|
||||
|
||||
describe('github owner/repo resolution', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { GitHubOwnerRepo, IssueSourcePreference } from '../../shared/types'
|
||||
import {
|
||||
githubRepoIdentityKey,
|
||||
isDefaultGitHubHost
|
||||
} from '../../shared/github-repository-identity-key'
|
||||
} from '../../shared/github/repository-identity-key'
|
||||
import {
|
||||
getOwnerRepoForRemote,
|
||||
ghRepoExecOptions,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IssueSourcePreference } from '../../shared/types'
|
||||
import { githubRepoIdentityKey } from '../../shared/github-repository-identity-key'
|
||||
import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key'
|
||||
import {
|
||||
getOwnerRepoForRemote,
|
||||
type LocalGitExecOptions,
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
PRReviewDecision,
|
||||
PRState
|
||||
} from '../../shared/types'
|
||||
import { githubRepoIdentityKey } from '../../shared/github-repository-identity-key'
|
||||
import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key'
|
||||
import { ghExecFileAsync } from '../git/runner'
|
||||
import {
|
||||
githubHostExecOptions,
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { normalizeGitHubRemoteHost } from '../../shared/git-remote-host-alias'
|
||||
import type { GitHubOwnerRepo } from '../../shared/types'
|
||||
|
||||
export type GitHubRemoteIdentity = GitHubOwnerRepo & { host: string }
|
||||
|
||||
export function normalizeGitHubRemoteHost(host: string): string {
|
||||
const normalizedHost = host.toLowerCase()
|
||||
// Why: GitHub documents ssh.github.com as SSH-over-HTTPS for github.com repos.
|
||||
return normalizedHost === 'ssh.github.com' ? 'github.com' : normalizedHost
|
||||
}
|
||||
|
||||
// Why: HTTP ports identify the GHES web/API endpoint; SSH and git ports are
|
||||
// transport-only and must not leak into gh's host identity.
|
||||
function hostFromRemoteUrl(url: URL): string {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ghExecFileAsync } from './gh-utils'
|
||||
import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit'
|
||||
import { githubHostExecOptions } from './github-api-repository'
|
||||
import { githubRepoIdentityKey } from '../../shared/github-repository-identity-key'
|
||||
import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key'
|
||||
import type { OwnerRepo } from './github-repository-identity'
|
||||
|
||||
type GhExecOptions = Parameters<typeof ghExecFileAsync>[1]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PRRefreshErrorType } from '../../shared/types'
|
||||
import { classifyGitHubUnavailable } from '../../shared/github-api-availability'
|
||||
import { classifyGitHubUnavailable } from '../../shared/github/api-availability'
|
||||
import { extractExecError } from '../git/exec-error'
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
GITHUB_PROJECT_REF_INPUT_MAX_BYTES,
|
||||
GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR
|
||||
} from '../../shared/github-project-ref-input'
|
||||
} from '../../shared/github/project-ref-input'
|
||||
import {
|
||||
PROJECT_VIEW_OWNER_CACHE_MAX_ENTRIES,
|
||||
_getProjectViewCacheSizesForTests,
|
||||
|
||||
@@ -46,12 +46,12 @@ import type {
|
||||
ListProjectViewsResult,
|
||||
ResolveProjectRefArgs,
|
||||
ResolveProjectRefResult
|
||||
} from '../../shared/github-project-types'
|
||||
} from '../../shared/github/project-types'
|
||||
import {
|
||||
GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR,
|
||||
isGitHubProjectRefInputTooLarge
|
||||
} from '../../shared/github-project-ref-input'
|
||||
import { githubProjectHost } from '../../shared/github-project-identity'
|
||||
} from '../../shared/github/project-ref-input'
|
||||
import { githubProjectHost } from '../../shared/github/project-identity'
|
||||
|
||||
// Re-export the public API so existing `./project-view` call sites keep working; the split is internal-only.
|
||||
export { isValidOwnerSlug, isValidRepoSlug } from './project-view/internals'
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
noteRepositoryRateLimitSpend,
|
||||
type RateLimitBucketKind
|
||||
} from '../rate-limit'
|
||||
import type { GitHubProjectViewError } from '../../../shared/github-project-types'
|
||||
import { githubProjectHost } from '../../../shared/github-project-identity'
|
||||
import { isDefaultGitHubHost } from '../../../shared/github-repository-identity-key'
|
||||
import type { GitHubProjectViewError } from '../../../shared/github/project-types'
|
||||
import { githubProjectHost } from '../../../shared/github/project-identity'
|
||||
import { isDefaultGitHubHost } from '../../../shared/github/repository-identity-key'
|
||||
import { isGitHubHostAuthenticatedForGlobalCli } from '../github-enterprise-repository'
|
||||
import {
|
||||
classifyProjectError,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
type GraphqlVars
|
||||
} from './internals'
|
||||
import { classifyProjectError, rateLimitedError } from './project-error-classification'
|
||||
import { githubProjectHost } from '../../../shared/github-project-identity'
|
||||
import { githubProjectHost } from '../../../shared/github/project-identity'
|
||||
import type { GitHubAssignableUser, GitHubWorkItemDetails, PRComment } from '../../../shared/types'
|
||||
import type {
|
||||
AddIssueCommentBySlugArgs,
|
||||
@@ -39,7 +39,7 @@ import type {
|
||||
UpdateIssueTypeBySlugArgs,
|
||||
UpdatePullRequestBySlugArgs,
|
||||
UpdateProjectItemFieldArgs
|
||||
} from '../../../shared/github-project-types'
|
||||
} from '../../../shared/github/project-types'
|
||||
|
||||
function githubHostExecOptions(args: { host?: string }): { host: string } {
|
||||
return { host: githubProjectHost(args.host) }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Why: turns raw gh stderr/stdout into the typed GitHubProjectViewError the
|
||||
// renderer can act on (auth vs scope vs rate limit vs drift), shared by the
|
||||
// project-view read and mutation paths.
|
||||
import type { GitHubProjectViewError } from '../../../shared/github-project-types'
|
||||
import { githubProjectHost } from '../../../shared/github-project-identity'
|
||||
import type { GitHubProjectViewError } from '../../../shared/github/project-types'
|
||||
import { githubProjectHost } from '../../../shared/github/project-identity'
|
||||
|
||||
export type GhGraphqlErrorShape = {
|
||||
type?: string
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
GitHubRateLimitBucket,
|
||||
GitHubRateLimitSnapshot
|
||||
} from '../../shared/types'
|
||||
import { isDefaultGitHubHost } from '../../shared/github-repository-identity-key'
|
||||
import { isDefaultGitHubHost } from '../../shared/github/repository-identity-key'
|
||||
import { isWslUncPath } from '../../shared/wsl-paths'
|
||||
import { acquire, release } from './gh-utils'
|
||||
import { ghExecFileAsync } from '../git/runner'
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
CreateStackedHostedReviewInput,
|
||||
CreateStackedHostedReviewResult
|
||||
} from '../../shared/hosted-review'
|
||||
import { isDefaultGitHubHost } from '../../shared/github-repository-identity-key'
|
||||
import { isDefaultGitHubHost } from '../../shared/github/repository-identity-key'
|
||||
import {
|
||||
normalizeHostedReviewBaseRef,
|
||||
normalizeHostedReviewHeadRef
|
||||
|
||||
+1
-1
@@ -267,7 +267,7 @@ import {
|
||||
configureWindowsHostGitEnvironmentReadiness,
|
||||
setDefaultWslDistroOverride
|
||||
} from './git/runner'
|
||||
import { getRepoIdFromWorktreeId } from '../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../shared/worktree/id'
|
||||
import { parseWorkspaceKey } from '../shared/workspace-scope'
|
||||
import { setMigrationUnsupportedPtyListener } from './agent-hooks/migration-unsupported-pty-state'
|
||||
import { AgentBrowserBridge } from './browser/agent-browser-bridge'
|
||||
|
||||
@@ -135,7 +135,7 @@ import {
|
||||
import { listRepoWorktrees } from '../repo-worktrees'
|
||||
import { recordCrashBreadcrumb } from '../crash-reporting/crash-breadcrumb-store'
|
||||
import { buildReadDirErrorBreadcrumb, type ReadDirThrowSite } from './readdir-error-diagnostics'
|
||||
import { splitWorktreeId } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId } from '../../shared/worktree/id'
|
||||
import { getRuntimePathBasename } from '../../shared/cross-platform-path'
|
||||
import type { LocalProjectWorktreeGitOptions } from '../project-runtime-git-options'
|
||||
import { registerLocalLogTailHandlers } from './local-log-tail'
|
||||
|
||||
@@ -107,7 +107,7 @@ import type {
|
||||
UpdateIssueTypeBySlugArgs,
|
||||
UpdateProjectItemFieldArgs,
|
||||
UpdatePullRequestBySlugArgs
|
||||
} from '../../shared/github-project-types'
|
||||
} from '../../shared/github/project-types'
|
||||
import { appStarSourceSchema } from '../../shared/gh-star-source'
|
||||
import { track } from '../telemetry/client'
|
||||
import { getCohortAtEmit } from '../telemetry/cohort-classifier'
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
} from '../linear/projects'
|
||||
import { listTeams, getTeamStates, getTeamLabels, getTeamMembers } from '../linear/teams'
|
||||
import type { LinearListFilter } from '../linear/issues'
|
||||
import { clampLinearIssueListLimit } from '../../shared/linear-issue-read-limits'
|
||||
import { optionalParsedLinearIssueAttributeFilter } from '../../shared/linear-issue-attribute-filter'
|
||||
import { clampLinearIssueListLimit } from '../../shared/linear/issue-read-limits'
|
||||
import { optionalParsedLinearIssueAttributeFilter } from '../../shared/linear/issue-attribute-filter'
|
||||
import type {
|
||||
LinearCustomViewModel,
|
||||
LinearIssueUpdate,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
getLocalProjectWorktreeGitOptions,
|
||||
type LocalProjectWorktreeGitOptions
|
||||
} from '../project-runtime-git-options'
|
||||
import { splitWorktreeId } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId } from '../../shared/worktree/id'
|
||||
import type { Repo } from '../../shared/types'
|
||||
|
||||
function comparableLocalPath(value: string): string {
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
NotificationSettings,
|
||||
NotificationSoundDataResult
|
||||
} from '../../shared/types'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree/id'
|
||||
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
|
||||
import { buildNotificationOptions } from './notification-options'
|
||||
import { readNotificationAuthorizationStatus } from './notification-authorization-status'
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@ import {
|
||||
type TerminalStartupCwdMissingDirFallback
|
||||
} from '../../shared/terminal-startup-cwd'
|
||||
import { isWslUncPath, toWindowsWslPath } from '../../shared/wsl-paths'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree-id'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
|
||||
import type { AgentSessionOwnerBinding } from '../../shared/agent-session-host-authority'
|
||||
import {
|
||||
agentSessionOwnerBindingsEqual,
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
} from '../../shared/remote-workspace-types'
|
||||
import type { SshTarget } from '../../shared/ssh-types'
|
||||
import type { WorkspaceSessionState } from '../../shared/types'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree/id'
|
||||
import { getRemoteWorkspaceNamespace } from './remote-workspace-namespace'
|
||||
import { registerRemoteWorkspaceNotificationHandler } from './remote-workspace-events'
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ import { prepareLocalWorktreeRootForRepo } from '../worktree-root-preparation'
|
||||
import {
|
||||
normalizeCustomWorktreeVisibilitySources,
|
||||
normalizeWorktreeVisibilitySourcePreferences
|
||||
} from '../../shared/worktree-visibility-sources'
|
||||
} from '../../shared/worktree/visibility-sources'
|
||||
import { runWithGitReadCacheInvalidation } from '../git/status'
|
||||
import { isAdmissibleDirectSshAuthority } from '../../shared/ssh-retained-payload-admission'
|
||||
import { isCurrentSshProviderAuthority } from '../ssh/ssh-provider-authority'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { resolve } from 'node:path'
|
||||
import type { Store } from '../persistence'
|
||||
import { isLinkedIssueNumber } from '../../shared/source-control-ai-action-variables'
|
||||
import type { WorktreeMeta } from '../../shared/types'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree-id'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
|
||||
|
||||
export type LinkedIssueLookupArgs = {
|
||||
worktreeId?: string
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
TerminalRemovalInProgressError,
|
||||
WatcherRemovalInProgressError
|
||||
} from './watcher-removal-gate'
|
||||
import { isWorktreeRemovalFenceError } from '../../shared/worktree-removal-fence-error'
|
||||
import { isWorktreeRemovalFenceError } from '../../shared/worktree/removal-fence-error'
|
||||
|
||||
describe('watcher removal gate', () => {
|
||||
it('waits for an existing install and rejects later equivalent-path installs', async () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import {
|
||||
TERMINAL_REMOVAL_IN_PROGRESS_MESSAGE,
|
||||
WATCHER_REMOVAL_IN_PROGRESS_MESSAGE
|
||||
} from '../../shared/worktree-removal-fence-error'
|
||||
} from '../../shared/worktree/removal-fence-error'
|
||||
|
||||
// Why: fence slots are identities, not a counter, so a removal that gives up waiting can drop exactly
|
||||
// the installs it waited on without a late finishInstall corrupting the count for a newer install.
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
createWorkspaceCleanupFingerprint,
|
||||
type WorkspaceCleanupCandidate
|
||||
} from '../../shared/workspace-cleanup'
|
||||
import { splitWorktreeId } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId } from '../../shared/worktree/id'
|
||||
import {
|
||||
getNewestWorkspaceCleanupDiffCommentAt,
|
||||
getWorkspaceCleanupInactivityReasonsForWorkspace,
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { IGitProvider } from '../providers/types'
|
||||
import { isFolderRepo } from '../../shared/repo-kind'
|
||||
import type { GitWorktreeInfo, Repo, Worktree } from '../../shared/types'
|
||||
import { mergeWorktree } from './worktree-logic'
|
||||
import { splitWorktreeId } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId } from '../../shared/worktree/id'
|
||||
import type {
|
||||
WorkspaceCleanupCandidate,
|
||||
WorkspaceCleanupScanError,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { posix } from 'node:path'
|
||||
import type { GlobalSettings } from '../../shared/types'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree-id'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree/id'
|
||||
import { computeWorktreePath } from './worktree-logic'
|
||||
|
||||
type WorktreePathSettings = Pick<GlobalSettings, 'nestWorkspaces' | 'workspaceDir'>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { resolveRuntimePath } from '../../shared/cross-platform-path'
|
||||
import { isSafeGitStatusUpstreamRef } from '../../shared/git-status-upstream-ref'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree/id'
|
||||
import {
|
||||
pathRelativeToWorktreeWatchRoot,
|
||||
type WorktreeBaseWatchTarget
|
||||
|
||||
@@ -2,7 +2,7 @@ import { resolve, relative, isAbsolute, posix, sep, win32 } from 'node:path'
|
||||
import type { GlobalSettings, OrcaWorkspaceLayout, Repo } from '../../shared/types'
|
||||
import { isWindowsAbsolutePathLike, resolveRuntimePath } from '../../shared/cross-platform-path'
|
||||
import { isWslUncPath } from '../../shared/wsl-paths'
|
||||
import { splitWorktreeId } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId } from '../../shared/worktree/id'
|
||||
import { replaceKnownEmojiWithShortcodes } from '../../shared/emoji-shortcode-catalog'
|
||||
import { getWslHome, parseWslPath } from '../wsl'
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import type { Store } from '../persistence'
|
||||
import type { GitPushTarget } from '../../shared/types'
|
||||
import { parseGitHubOwnerRepo } from '../github/gh-utils'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree/id'
|
||||
import { iterateProcessOutputLines } from '../../shared/process-output-field-scanner'
|
||||
|
||||
export type GitRemoteExec = (
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
import { resolveLocalGitUsername, getSshGitUsername } from '../git/git-username'
|
||||
import { hasCommitObjectViaGitExec } from '../git/commit-object-ref'
|
||||
import { resolveWorktreeCreateBase } from '../worktree-create-base'
|
||||
import { resolveWorktreeAddBaseRef } from '../../shared/worktree-base-ref'
|
||||
import { resolveWorktreeAddBaseRef } from '../../shared/worktree/base-ref'
|
||||
import { getHostedReviewForBranch } from '../source-control/hosted-review'
|
||||
import type { ForgeProviderId } from '../source-control/forge-provider'
|
||||
import { validateGitPushTarget } from '../git/push-target-validation'
|
||||
@@ -88,7 +88,7 @@ import {
|
||||
} from './worktree-logic'
|
||||
import { findCreatedWorktree } from './created-worktree-reconciliation'
|
||||
import type { BranchPrefixSettings } from '../../shared/branch-prefix'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../../shared/worktree/id'
|
||||
import { parseWorkspaceKey, worktreeWorkspaceKey } from '../../shared/workspace-scope'
|
||||
import {
|
||||
cleanupUnusedWorktreePushTargetRemoteWithExec,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { isFolderRepo } from '../../shared/repo-kind'
|
||||
import { readBranchRenameFailureOutputForDisplay } from '../agent-hooks/branch-rename-failure-output'
|
||||
import { parseWorkspaceKey } from '../../shared/workspace-scope'
|
||||
import { inspectSetupScriptImportCandidates } from '../../shared/setup-script-imports'
|
||||
import { planWorktreeSortOrderUpdates } from '../../shared/worktree-sort-order-update'
|
||||
import { planWorktreeSortOrderUpdates } from '../../shared/worktree/sort-order-update'
|
||||
import { getProjectHostSetupWorktreeMeta } from '../../shared/project-host-setup-projection'
|
||||
import { TaskSourceContextSchema } from '../../shared/task-source-context-schema'
|
||||
import { WorkspaceLinkedItemSchema } from '../../shared/workspace-linked-item-schema'
|
||||
@@ -37,7 +37,7 @@ import type {
|
||||
WorkspaceLineage,
|
||||
WorktreeMeta
|
||||
} from '../../shared/types'
|
||||
import { assertWorktreeUnlockedForRemoval } from '../../shared/worktree-removal'
|
||||
import { assertWorktreeUnlockedForRemoval } from '../../shared/worktree/removal'
|
||||
import {
|
||||
getRepoExecutionHostId,
|
||||
LOCAL_EXECUTION_HOST_ID,
|
||||
@@ -66,11 +66,11 @@ import {
|
||||
buildKnownOrcaWorkspaceLayouts,
|
||||
isLegacyRepoForExternalWorktreeVisibility,
|
||||
toDetectedWorktree
|
||||
} from '../../shared/worktree-ownership'
|
||||
} from '../../shared/worktree/ownership'
|
||||
import {
|
||||
createWorktreeVisibilitySourceMatcher,
|
||||
resolveCustomWorktreeVisibilitySources
|
||||
} from '../../shared/worktree-visibility-sources'
|
||||
} from '../../shared/worktree/visibility-sources'
|
||||
import {
|
||||
assertWorktreeCleanForRemoval,
|
||||
forceDeleteLocalBranch,
|
||||
@@ -239,7 +239,7 @@ import {
|
||||
FOLDER_WORKSPACE_INSTANCE_SEPARATOR,
|
||||
getRepoIdFromWorktreeId,
|
||||
getWorktreePathBasenameFromId
|
||||
} from '../../shared/worktree-id'
|
||||
} from '../../shared/worktree/id'
|
||||
import { prefetchWorktreeCreateBase } from '../worktree-create-base-prefetch'
|
||||
import {
|
||||
getLocalProjectGitExecOptions,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {
|
||||
LinearIssueActivityEntry,
|
||||
LinearIssueActivityValue
|
||||
} from '../../shared/linear-issue-activity'
|
||||
} from '../../shared/linear/issue-activity'
|
||||
import type { RawNamedEntity, RawPageInfo, RawUser } from './issue-context-raw'
|
||||
|
||||
type RawActivityEntity = RawNamedEntity & {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LinearSearchIssueSummary, LinearSearchResult } from '../../shared/linear-agent-access'
|
||||
import { clampLinearSearchLimit } from '../../shared/linear-agent-access'
|
||||
import type { LinearSearchIssueSummary, LinearSearchResult } from '../../shared/linear/agent-access'
|
||||
import { clampLinearSearchLimit } from '../../shared/linear/agent-access'
|
||||
import type { LinearWorkspace } from '../../shared/types'
|
||||
import {
|
||||
acquire,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseLinearIssueInput } from '../../shared/linear-links'
|
||||
import { parseLinearIssueInput } from '../../shared/linear/links'
|
||||
import { getConnectedWorkspaces } from './issue-context-client'
|
||||
import { linearError } from './issue-context-errors'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LinearErrorCode, LinearIncludeErrorCode } from '../../shared/linear-agent-access'
|
||||
import type { LinearErrorCode, LinearIncludeErrorCode } from '../../shared/linear/agent-access'
|
||||
|
||||
export class LinearAgentAccessError extends Error {
|
||||
readonly code: LinearErrorCode
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LinearErrorCode, LinearWorkspaceCandidate } from '../../shared/linear-agent-access'
|
||||
import type { LinearErrorCode, LinearWorkspaceCandidate } from '../../shared/linear/agent-access'
|
||||
import type { LinearWorkspace } from '../../shared/types'
|
||||
import { getClients, getStatus, type LinearClientForWorkspace } from './client'
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { LinearIssueContextResult, LinearIssueRequest } from '../../shared/linear-agent-access'
|
||||
import type { LinearIssueContextResult, LinearIssueRequest } from '../../shared/linear/agent-access'
|
||||
import type { ResolvedIssue } from './issue-context-client'
|
||||
import { ACTIVITY_QUERY } from './issue-activity-raw'
|
||||
import {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
LinearIssueContextResult,
|
||||
LinearIssueInclude,
|
||||
LinearIssueRequest
|
||||
} from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import {
|
||||
LINEAR_ATTACHMENTS_CAP,
|
||||
LINEAR_ACTIVITY_CAP,
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
LINEAR_COMMENTS_CAP,
|
||||
LINEAR_COMMENT_BODY_CAP,
|
||||
clampLinearIssueDepth
|
||||
} from '../../shared/linear-agent-access'
|
||||
import { extractLinearInlineMedia } from '../../shared/linear-inline-media'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import { extractLinearInlineMedia } from '../../shared/linear/inline-media'
|
||||
import type { ResolvedIssue } from './issue-context-client'
|
||||
import { getRequiredEntry, withLinearRead } from './issue-context-client'
|
||||
import { getPublicFileUrlClient } from './client'
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
LinearIssueCommentNode,
|
||||
LinearIssueContextResult,
|
||||
LinearIssueSummary
|
||||
} from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import { collectInlineMedia } from './issue-context'
|
||||
|
||||
function summary(overrides: Partial<LinearIssueSummary>): LinearIssueSummary {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LINEAR_ISSUE_API_PAGE_SIZE_MAX } from '../../shared/linear-issue-read-limits'
|
||||
import { LINEAR_ISSUE_API_PAGE_SIZE_MAX } from '../../shared/linear/issue-read-limits'
|
||||
|
||||
export type LinearPageVariables = { first: number; after?: string }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
LinearCollectionMeta,
|
||||
LinearIssueSummary,
|
||||
LinearSearchIssueSummary
|
||||
} from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
|
||||
export type RawIssueResponse = {
|
||||
issue?: RawIssue | null
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LinearCollectionMeta, LinearIssueRelation } from '../../shared/linear-agent-access'
|
||||
import { LINEAR_RELATIONS_CAP } from '../../shared/linear-agent-access'
|
||||
import type { LinearCollectionMeta, LinearIssueRelation } from '../../shared/linear/agent-access'
|
||||
import { LINEAR_RELATIONS_CAP } from '../../shared/linear/agent-access'
|
||||
import type { ResolvedIssue } from './issue-context-client'
|
||||
import { getRequiredEntry, withLinearRead } from './issue-context-client'
|
||||
import { readConnectionPages } from './issue-context-pagination'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LinearWorkspaceCandidate } from '../../shared/linear-agent-access'
|
||||
import type { LinearWorkspaceCandidate } from '../../shared/linear/agent-access'
|
||||
import type { LinearWorkspace } from '../../shared/types'
|
||||
import { linearError } from './issue-context-errors'
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import type {
|
||||
LinearIssueChildNode,
|
||||
LinearIssueContextResult,
|
||||
LinearIssueRequest
|
||||
} from '../../shared/linear-agent-access'
|
||||
import { extractLinearInlineMedia } from '../../shared/linear-inline-media'
|
||||
import { parseLinearIssueInput } from '../../shared/linear-links'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import { extractLinearInlineMedia } from '../../shared/linear/inline-media'
|
||||
import { parseLinearIssueInput } from '../../shared/linear/links'
|
||||
import {
|
||||
resolveIssue,
|
||||
searchLinearIssuesForAgents,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
isEmptyLinearIssueAttributeFilter,
|
||||
type LinearIssueAttributeFilter
|
||||
} from '../../shared/linear-issue-attribute-filter'
|
||||
} from '../../shared/linear/issue-attribute-filter'
|
||||
import type { LinearListFilter } from './issues'
|
||||
|
||||
// Why: attribute facets must merge into Linear's IssueFilter before the first-N
|
||||
|
||||
@@ -3,8 +3,8 @@ import { loadLinearSdk } from './linear-sdk'
|
||||
import type {
|
||||
LinearIssueRelationship,
|
||||
LinearIssueRelationWriteResult
|
||||
} from '../../shared/linear-issue-relation-write'
|
||||
import { LINEAR_ISSUE_API_PAGE_SIZE_MAX } from '../../shared/linear-issue-read-limits'
|
||||
} from '../../shared/linear/issue-relation-write'
|
||||
import { LINEAR_ISSUE_API_PAGE_SIZE_MAX } from '../../shared/linear/issue-read-limits'
|
||||
import { acquire, clearToken, getClients, isAuthError, release } from './client'
|
||||
import { linearError } from './issue-context-errors'
|
||||
import {
|
||||
|
||||
@@ -14,11 +14,11 @@ import { loadLinearSdk } from './linear-sdk'
|
||||
import {
|
||||
LINEAR_ISSUE_API_PAGE_SIZE_MAX,
|
||||
clampLinearIssueListLimit
|
||||
} from '../../shared/linear-issue-read-limits'
|
||||
} from '../../shared/linear/issue-read-limits'
|
||||
import {
|
||||
isEmptyLinearIssueAttributeFilter,
|
||||
type LinearIssueAttributeFilter
|
||||
} from '../../shared/linear-issue-attribute-filter'
|
||||
} from '../../shared/linear/issue-attribute-filter'
|
||||
import {
|
||||
acquire,
|
||||
release,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LinearLabel, LinearMember, LinearTeam, LinearWorkflowState } from '../../shared/types'
|
||||
import { buildLinearTeamUrl } from '../../shared/linear-links'
|
||||
import { buildLinearTeamUrl } from '../../shared/linear/links'
|
||||
import type { LinearClientForWorkspace } from './client'
|
||||
|
||||
const TEAM_PAGE_SIZE = 100
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {
|
||||
LinearMcpIssueListRequest,
|
||||
LinearMcpIssueListResult
|
||||
} from '../../shared/linear-agent-access'
|
||||
} from '../../shared/linear/agent-access'
|
||||
import { getClients, getStatus, type LinearClientForWorkspace } from './client'
|
||||
import { withLinearRead } from './issue-context-client'
|
||||
import { linearError } from './issue-context-errors'
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
import {
|
||||
LINEAR_ISSUE_API_PAGE_SIZE_MAX,
|
||||
clampLinearIssueListLimit
|
||||
} from '../../shared/linear-issue-read-limits'
|
||||
} from '../../shared/linear/issue-read-limits'
|
||||
import {
|
||||
acquire,
|
||||
clearToken,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
hasCachedWslAvailability,
|
||||
hasCachedWslDistros
|
||||
} from './wsl'
|
||||
import { getRepoIdFromWorktreeId } from '../shared/worktree-id'
|
||||
import { getRepoIdFromWorktreeId } from '../shared/worktree/id'
|
||||
import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../shared/execution-host'
|
||||
|
||||
function canResolveProjectRuntimeForRepo(store: Store): boolean {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GitWorktreeInfo, RemoveWorktreeResult } from '../shared/types'
|
||||
import { assertWorktreeUnlockedForRemoval } from '../shared/worktree-removal'
|
||||
import { assertWorktreeUnlockedForRemoval } from '../shared/worktree/removal'
|
||||
import { areWorktreePathsEqual, formatWorktreeRemovalError } from './ipc/worktree-logic'
|
||||
import { gitExecFileAsync } from './git/runner'
|
||||
import { listWorktreesStrict, type GitWorktreeExecOptions } from './git/worktree'
|
||||
|
||||
@@ -21,7 +21,7 @@ import { basename } from 'node:path'
|
||||
import { exec } from 'node:child_process'
|
||||
import { promisify } from 'node:util'
|
||||
import os from 'node:os'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree-id'
|
||||
import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
|
||||
import {
|
||||
getProcessOutputFields,
|
||||
iterateProcessOutputLines
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { SessionInfo } from '../daemon/types'
|
||||
import { listRegisteredPtys, registerPty } from './pty-registry'
|
||||
import { listRepoWorktrees } from '../repo-worktrees'
|
||||
import { parsePtySessionId } from '../../shared/pty-session-id-format'
|
||||
import { splitWorktreeId } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId } from '../../shared/worktree/id'
|
||||
import type { Store } from '../persistence'
|
||||
|
||||
// Why: macOS dock reactivation reruns setup; avoid repeating Git I/O and daemon RPC.
|
||||
|
||||
@@ -130,4 +130,48 @@ describe('Codex WSL scan gate', () => {
|
||||
})
|
||||
).rejects.toBe(refusal)
|
||||
})
|
||||
|
||||
it('reports the abort, not a refusal, when the hook-path probe races a caller abort', async () => {
|
||||
const controller = new AbortController()
|
||||
const abortReason = new Error('caller went away')
|
||||
mocks.gate.mockImplementation(async (options: { operation?: string; path: string }) => {
|
||||
if (options.operation === 'access') {
|
||||
controller.abort(abortReason)
|
||||
throw new WslTranscriptFsError('timeout', 'slow share')
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
await expect(
|
||||
resolveSessionFilePath(
|
||||
'codex',
|
||||
'session-id',
|
||||
{
|
||||
transcriptPath: `${WSL_SESSIONS_DIR}\\2026\\rollout-1-session-id.jsonl`,
|
||||
codexSessionsDirs: [LOCAL_SESSIONS_DIR]
|
||||
},
|
||||
controller.signal
|
||||
)
|
||||
).rejects.toBe(abortReason)
|
||||
})
|
||||
|
||||
// The scan layer's own waiter already wins this race; the resolver's post-catch
|
||||
// signal check is the backstop if that ever stops holding.
|
||||
it('reports the abort, not a refusal, when a scanned root is aborted mid-scan', async () => {
|
||||
const controller = new AbortController()
|
||||
const abortReason = new Error('caller went away')
|
||||
mocks.gate.mockImplementation(async () => {
|
||||
controller.abort(abortReason)
|
||||
throw new WslTranscriptFsError('timeout', 'slow share')
|
||||
})
|
||||
|
||||
await expect(
|
||||
resolveSessionFilePath(
|
||||
'codex',
|
||||
'session-id',
|
||||
{ codexSessionsDirs: [WSL_SESSIONS_DIR] },
|
||||
controller.signal
|
||||
)
|
||||
).rejects.toBe(abortReason)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -108,6 +108,8 @@ export async function resolveSessionFilePath(
|
||||
return hostReadable
|
||||
}
|
||||
} catch (error) {
|
||||
// A caller abort that races the refusal stays authoritative.
|
||||
signal?.throwIfAborted()
|
||||
// Why: the id-based search may still hit; surface the refusal only when
|
||||
// it does not, so a stalled distro reads as unavailable, never "missing".
|
||||
unavailable = wslTranscriptFsRefusal(error)
|
||||
@@ -232,6 +234,8 @@ async function findCodexRolloutInDirs(
|
||||
return files
|
||||
}
|
||||
} catch (error) {
|
||||
// A caller abort that races the refusal stays authoritative.
|
||||
signal?.throwIfAborted()
|
||||
// Why: one stalled distro must not hide another root's hit.
|
||||
unavailable = wslTranscriptFsRefusal(error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getDefaultPersistedState } from '../../shared/constants'
|
||||
import type { PersistedState, Project, ProjectHostSetup, Repo } from '../../shared/types'
|
||||
import { rebuildRepoBackedProjectState } from './profile-project-state-file'
|
||||
|
||||
const upstreamIdentity = {
|
||||
canonicalKey: 'git.example.com/acme/app-upstream',
|
||||
remoteName: 'upstream',
|
||||
remoteUrl: 'git@git.example.com:acme/app-upstream.git'
|
||||
}
|
||||
|
||||
const makeRepo = (overrides: Partial<Repo> = {}): Repo => ({
|
||||
id: 'r1',
|
||||
path: '/repo',
|
||||
displayName: 'App',
|
||||
badgeColor: '#fff',
|
||||
addedAt: 1,
|
||||
...overrides
|
||||
})
|
||||
|
||||
const makeProject = (overrides: Partial<Project> = {}): Project => ({
|
||||
id: 'project-1',
|
||||
displayName: 'App',
|
||||
badgeColor: '#737373',
|
||||
sourceRepoIds: [],
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
...overrides
|
||||
})
|
||||
|
||||
const makeSetup = (overrides: Partial<ProjectHostSetup> = {}): ProjectHostSetup => ({
|
||||
id: 'setup-1',
|
||||
projectId: 'project-1',
|
||||
hostId: 'local',
|
||||
repoId: '',
|
||||
path: '/repo',
|
||||
displayName: 'App',
|
||||
setupState: 'ready',
|
||||
setupMethod: 'imported-existing-folder',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
...overrides
|
||||
})
|
||||
|
||||
function makeState(overrides: Partial<PersistedState>): PersistedState {
|
||||
return { ...getDefaultPersistedState('/home/test'), ...overrides }
|
||||
}
|
||||
|
||||
describe('rebuildRepoBackedProjectState', () => {
|
||||
it('carries project state and independent setups across a repo remote identity change', () => {
|
||||
const originProjectId = 'git:git.example.com/acme/app'
|
||||
const rebuilt = rebuildRepoBackedProjectState(
|
||||
makeState({
|
||||
repos: [makeRepo({ gitRemoteIdentity: upstreamIdentity })],
|
||||
projects: [
|
||||
makeProject({
|
||||
id: originProjectId,
|
||||
sourceRepoIds: ['r1'],
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
|
||||
})
|
||||
],
|
||||
projectHostSetups: [
|
||||
makeSetup({ id: 'r1', projectId: originProjectId, repoId: 'r1' }),
|
||||
makeSetup({
|
||||
id: 'app::gpu-vm',
|
||||
projectId: originProjectId,
|
||||
hostId: 'runtime:gpu-vm',
|
||||
path: '/srv/app'
|
||||
})
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
expect(rebuilt.projects).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'git:git.example.com/acme/app-upstream',
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
|
||||
})
|
||||
])
|
||||
expect(rebuilt.projectHostSetups.find((setup) => setup.id === 'app::gpu-vm')?.projectId).toBe(
|
||||
'git:git.example.com/acme/app-upstream'
|
||||
)
|
||||
})
|
||||
|
||||
it('picks one predecessor project when several prior rows overlap the same repos', () => {
|
||||
const sharedIdentity = {
|
||||
canonicalKey: 'git.example.com/acme/shared',
|
||||
remoteName: 'origin',
|
||||
remoteUrl: 'git@git.example.com:acme/shared.git'
|
||||
}
|
||||
const rebuilt = rebuildRepoBackedProjectState(
|
||||
makeState({
|
||||
repos: [
|
||||
makeRepo({ id: 'r1', path: '/left', gitRemoteIdentity: sharedIdentity }),
|
||||
makeRepo({ id: 'r2', path: '/right', gitRemoteIdentity: sharedIdentity })
|
||||
],
|
||||
projects: [
|
||||
makeProject({
|
||||
id: 'git:git.example.com/acme/left',
|
||||
sourceRepoIds: ['r1'],
|
||||
updatedAt: 200,
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
|
||||
}),
|
||||
makeProject({
|
||||
id: 'git:git.example.com/acme/right',
|
||||
sourceRepoIds: ['r2'],
|
||||
updatedAt: 100,
|
||||
localWindowsRuntimePreference: { kind: 'windows-host' }
|
||||
})
|
||||
],
|
||||
projectHostSetups: [
|
||||
makeSetup({ id: 'r1', projectId: 'git:git.example.com/acme/left', repoId: 'r1' }),
|
||||
makeSetup({ id: 'r2', projectId: 'git:git.example.com/acme/right', repoId: 'r2' })
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
// Equal repo overlap resolves by newest updatedAt; the loser's preference is never merged in.
|
||||
expect(rebuilt.projects).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'git:git.example.com/acme/shared',
|
||||
sourceRepoIds: ['r1', 'r2'],
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
it('leaves an unclaimed prior project standing with its own independent setups', () => {
|
||||
const rebuilt = rebuildRepoBackedProjectState(
|
||||
makeState({
|
||||
repos: [makeRepo({ gitRemoteIdentity: upstreamIdentity })],
|
||||
projects: [
|
||||
makeProject({
|
||||
id: 'cloud-project',
|
||||
localWindowsRuntimePreference: { kind: 'windows-host' }
|
||||
})
|
||||
],
|
||||
projectHostSetups: [
|
||||
makeSetup({ id: 'cloud-project::gpu-vm', projectId: 'cloud-project', path: '/srv/cloud' })
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
expect(rebuilt.projects.map((project) => project.id)).toEqual([
|
||||
'git:git.example.com/acme/app-upstream',
|
||||
'cloud-project'
|
||||
])
|
||||
expect(rebuilt.projectHostSetups.map((setup) => setup.projectId)).toEqual([
|
||||
'git:git.example.com/acme/app-upstream',
|
||||
'cloud-project'
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -5,6 +5,7 @@ import { dirname } from 'node:path'
|
||||
import { getDefaultPersistedState, getDefaultWorkspaceSession } from '../../shared/constants'
|
||||
import type { ExecutionHostId } from '../../shared/execution-host'
|
||||
import { projectHostSetupProjectionFromRepos } from '../../shared/project-host-setup-projection'
|
||||
import { carryProjectStateThroughIdentityChange } from '../../shared/project-identity-succession'
|
||||
import type {
|
||||
PersistedState,
|
||||
Project,
|
||||
@@ -102,16 +103,22 @@ function isRepoBackedProjectHostSetup(
|
||||
|
||||
export function rebuildRepoBackedProjectState(state: TransferProfileState): TransferProfileState {
|
||||
const projection = projectHostSetupProjectionFromRepos(state.repos)
|
||||
const existingProjectsById = new Map(state.projects.map((project) => [project.id, project]))
|
||||
const succession = carryProjectStateThroughIdentityChange(projection.projects, state.projects)
|
||||
const currentRepoIds = new Set(state.repos.map((repo) => repo.id))
|
||||
const projectedProjectIds = new Set(projection.projects.map((project) => project.id))
|
||||
const projectedSetupIds = new Set(projection.setups.map((setup) => setup.id))
|
||||
const independentSetups = state.projectHostSetups.filter((setup) => {
|
||||
if (projectedSetupIds.has(setup.id)) {
|
||||
return false
|
||||
}
|
||||
return !isRepoBackedProjectHostSetup(setup, currentRepoIds)
|
||||
})
|
||||
const independentSetups = state.projectHostSetups
|
||||
.filter((setup) => {
|
||||
if (projectedSetupIds.has(setup.id)) {
|
||||
return false
|
||||
}
|
||||
return !isRepoBackedProjectHostSetup(setup, currentRepoIds)
|
||||
})
|
||||
// Why: follow the repo's project through a derived-id change so no ghost project row survives.
|
||||
.map((setup) => {
|
||||
const remappedProjectId = succession.remappedProjectIds.get(setup.projectId)
|
||||
return remappedProjectId ? { ...setup, projectId: remappedProjectId } : setup
|
||||
})
|
||||
const independentProjectIds = new Set(independentSetups.map((setup) => setup.projectId))
|
||||
const independentProjects = state.projects
|
||||
.filter(
|
||||
@@ -121,19 +128,9 @@ export function rebuildRepoBackedProjectState(state: TransferProfileState): Tran
|
||||
...project,
|
||||
sourceRepoIds: project.sourceRepoIds.filter((repoId) => currentRepoIds.has(repoId))
|
||||
}))
|
||||
const projectedProjects = projection.projects.map((project) => {
|
||||
const existingProject = existingProjectsById.get(project.id)
|
||||
return existingProject?.localWindowsRuntimePreference
|
||||
? {
|
||||
...project,
|
||||
localWindowsRuntimePreference: existingProject.localWindowsRuntimePreference,
|
||||
updatedAt: Math.max(project.updatedAt, existingProject.updatedAt)
|
||||
}
|
||||
: project
|
||||
})
|
||||
return {
|
||||
...state,
|
||||
projects: [...projectedProjects, ...independentProjects],
|
||||
projects: [...succession.projects, ...independentProjects],
|
||||
projectHostSetups: [...projection.setups, ...independentSetups]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { normalizeRuntimePathForComparison } from '../../shared/cross-platform-p
|
||||
import { getRepoExecutionHostId } from '../../shared/execution-host'
|
||||
import type { Repo, WorkspaceKey } from '../../shared/types'
|
||||
import { parseWorkspaceKey, worktreeWorkspaceKey } from '../../shared/workspace-scope'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree-id'
|
||||
import { WORKTREE_ID_SEPARATOR } from '../../shared/worktree/id'
|
||||
|
||||
export function repoPhysicalKey(
|
||||
repo: Pick<Repo, 'path' | 'connectionId' | 'executionHostId'>
|
||||
|
||||
@@ -595,6 +595,124 @@ describe('Store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('carries project state and independent setups across a repo remote identity change', async () => {
|
||||
const originProjectId = 'git:git.example.com/acme/app'
|
||||
writeDataFile({
|
||||
...getDefaultPersistedState(testState.dir),
|
||||
repos: [
|
||||
makeRepo({
|
||||
id: 'r1',
|
||||
path: '/repo',
|
||||
displayName: 'App',
|
||||
gitRemoteIdentity: {
|
||||
canonicalKey: 'git.example.com/acme/app',
|
||||
remoteName: 'origin',
|
||||
remoteUrl: 'git@git.example.com:acme/app.git'
|
||||
}
|
||||
})
|
||||
],
|
||||
projects: [
|
||||
makeProject({
|
||||
id: originProjectId,
|
||||
sourceRepoIds: ['r1'],
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
|
||||
})
|
||||
],
|
||||
projectHostSetups: [
|
||||
makeProjectHostSetup({ id: 'r1', projectId: originProjectId, repoId: 'r1' }),
|
||||
makeProjectHostSetup({
|
||||
id: 'app::gpu-vm',
|
||||
projectId: originProjectId,
|
||||
hostId: 'runtime:gpu-vm',
|
||||
repoId: '',
|
||||
path: '/srv/app'
|
||||
})
|
||||
]
|
||||
})
|
||||
const store = await createStore()
|
||||
|
||||
// A re-probe that now prefers the `upstream` remote rewrites the derived project id.
|
||||
store.updateRepo('r1', {
|
||||
gitRemoteIdentity: {
|
||||
canonicalKey: 'git.example.com/acme/app-upstream',
|
||||
remoteName: 'upstream',
|
||||
remoteUrl: 'git@git.example.com:acme/app-upstream.git'
|
||||
}
|
||||
})
|
||||
|
||||
const upstreamProjectId = 'git:git.example.com/acme/app-upstream'
|
||||
expect(store.getProjects().map((project) => project.id)).toEqual([upstreamProjectId])
|
||||
expect(store.getProjects()[0]?.localWindowsRuntimePreference).toEqual({
|
||||
kind: 'wsl',
|
||||
distro: 'Ubuntu'
|
||||
})
|
||||
expect(
|
||||
store.getProjectHostSetups().find((setup) => setup.id === 'app::gpu-vm')?.projectId
|
||||
).toBe(upstreamProjectId)
|
||||
})
|
||||
|
||||
it('picks one predecessor project when several prior rows overlap the same repos', async () => {
|
||||
const sharedIdentity = {
|
||||
canonicalKey: 'git.example.com/acme/shared',
|
||||
remoteName: 'origin',
|
||||
remoteUrl: 'git@git.example.com:acme/shared.git'
|
||||
}
|
||||
writeDataFile({
|
||||
...getDefaultPersistedState(testState.dir),
|
||||
repos: [
|
||||
makeRepo({
|
||||
id: 'r1',
|
||||
path: '/left',
|
||||
displayName: 'Left',
|
||||
gitRemoteIdentity: sharedIdentity
|
||||
}),
|
||||
makeRepo({
|
||||
id: 'r2',
|
||||
path: '/right',
|
||||
displayName: 'Right',
|
||||
gitRemoteIdentity: sharedIdentity
|
||||
})
|
||||
],
|
||||
projects: [
|
||||
makeProject({
|
||||
id: 'git:git.example.com/acme/left',
|
||||
sourceRepoIds: ['r1'],
|
||||
updatedAt: 200,
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
|
||||
}),
|
||||
makeProject({
|
||||
id: 'git:git.example.com/acme/right',
|
||||
sourceRepoIds: ['r2'],
|
||||
updatedAt: 100,
|
||||
localWindowsRuntimePreference: { kind: 'windows-host' }
|
||||
})
|
||||
],
|
||||
projectHostSetups: [
|
||||
makeProjectHostSetup({
|
||||
id: 'r1',
|
||||
projectId: 'git:git.example.com/acme/left',
|
||||
repoId: 'r1'
|
||||
}),
|
||||
makeProjectHostSetup({
|
||||
id: 'r2',
|
||||
projectId: 'git:git.example.com/acme/right',
|
||||
repoId: 'r2'
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const store = await createStore()
|
||||
|
||||
// Equal repo overlap resolves by newest updatedAt; the loser's preference is never merged in.
|
||||
expect(store.getProjects()).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'git:git.example.com/acme/shared',
|
||||
sourceRepoIds: ['r1', 'r2'],
|
||||
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
it('migrates legacy WSL agent settings into the global Windows runtime default', async () => {
|
||||
writeDataFile({
|
||||
schemaVersion: 1,
|
||||
|
||||
+20
-22
@@ -79,6 +79,7 @@ import {
|
||||
normalizeProjectRuntimePreference
|
||||
} from '../shared/project-execution-runtime'
|
||||
import { projectHostSetupProjectionFromRepos } from '../shared/project-host-setup-projection'
|
||||
import { carryProjectStateThroughIdentityChange } from '../shared/project-identity-succession'
|
||||
import { isPluginPanelTabKey } from '../shared/plugins/plugin-manifest'
|
||||
import type { GitRemoteIdentity } from '../shared/git-remote-identity'
|
||||
import {
|
||||
@@ -140,7 +141,7 @@ import { normalizeStatusBarUsageMode } from '../shared/status-bar-usage-mode'
|
||||
import {
|
||||
normalizeCustomWorktreeVisibilitySources,
|
||||
normalizeWorktreeVisibilitySourcePreferences
|
||||
} from '../shared/worktree-visibility-sources'
|
||||
} from '../shared/worktree/visibility-sources'
|
||||
import { isExistingPersistedProfile } from '../shared/project-order-manual-default-notice'
|
||||
import { resolveUsagePercentageDisplayChangeNoticeDismissed } from '../shared/usage-percentage-display-change-notice'
|
||||
import { normalizePRBotAuthorOverrides } from '../shared/pr-bot-author-overrides'
|
||||
@@ -172,7 +173,7 @@ import {
|
||||
FOLDER_WORKSPACE_INSTANCE_SEPARATOR,
|
||||
getRepoIdFromWorktreeId,
|
||||
getWorktreePathBasenameFromId
|
||||
} from '../shared/worktree-id'
|
||||
} from '../shared/worktree/id'
|
||||
import {
|
||||
isPathInsideOrEqual,
|
||||
isWindowsAbsolutePathLike,
|
||||
@@ -219,7 +220,7 @@ import {
|
||||
} from '../shared/workspace-statuses'
|
||||
import { clampMarkdownTocPanelWidth } from '../shared/markdown-toc-panel-width'
|
||||
import { clampCombinedDiffFileTreeWidth } from '../shared/combined-diff-file-tree-width'
|
||||
import { isLegacyRepoForExternalWorktreeVisibility } from '../shared/worktree-ownership'
|
||||
import { isLegacyRepoForExternalWorktreeVisibility } from '../shared/worktree/ownership'
|
||||
import {
|
||||
migrateExternalWorktreeVisibilityDefaults,
|
||||
normalizeWorktreeVisibilityDefaults
|
||||
@@ -2470,19 +2471,26 @@ function mergeProjectHostSetupCompatibilityState(
|
||||
repos: readonly Repo[]
|
||||
): Pick<PersistedState, 'projects' | 'projectHostSetups'> {
|
||||
const projection = projectHostSetupProjectionFromRepos(repos)
|
||||
const existingProjectsById = new Map(
|
||||
(state.projects ?? []).map((project) => [project.id, project])
|
||||
const succession = carryProjectStateThroughIdentityChange(
|
||||
projection.projects,
|
||||
state.projects ?? []
|
||||
)
|
||||
const currentRepoIds = new Set(repos.map((repo) => repo.id))
|
||||
const projectedProjectIds = new Set(projection.projects.map((project) => project.id))
|
||||
const projectedSetupIds = new Set(projection.setups.map((setup) => setup.id))
|
||||
// Why: legacy/repo-backed setup rows reuse the repo id; keep only independent rows so repo deletion leaves no ghosts.
|
||||
const independentSetups = (state.projectHostSetups ?? []).filter((setup) => {
|
||||
if (projectedSetupIds.has(setup.id)) {
|
||||
return false
|
||||
}
|
||||
return !isRepoBackedProjectHostSetup(setup, currentRepoIds)
|
||||
})
|
||||
const independentSetups = (state.projectHostSetups ?? [])
|
||||
.filter((setup) => {
|
||||
if (projectedSetupIds.has(setup.id)) {
|
||||
return false
|
||||
}
|
||||
return !isRepoBackedProjectHostSetup(setup, currentRepoIds)
|
||||
})
|
||||
// Why: follow the repo's project through a derived-id change so no ghost project row survives.
|
||||
.map((setup) => {
|
||||
const remappedProjectId = succession.remappedProjectIds.get(setup.projectId)
|
||||
return remappedProjectId ? { ...setup, projectId: remappedProjectId } : setup
|
||||
})
|
||||
const independentProjectIds = new Set(independentSetups.map((setup) => setup.projectId))
|
||||
const independentProjects = (state.projects ?? [])
|
||||
.filter(
|
||||
@@ -2492,18 +2500,8 @@ function mergeProjectHostSetupCompatibilityState(
|
||||
...project,
|
||||
sourceRepoIds: project.sourceRepoIds.filter((repoId) => currentRepoIds.has(repoId))
|
||||
}))
|
||||
const projectedProjects = projection.projects.map((project) => {
|
||||
const existingProject = existingProjectsById.get(project.id)
|
||||
return existingProject?.localWindowsRuntimePreference
|
||||
? {
|
||||
...project,
|
||||
localWindowsRuntimePreference: existingProject.localWindowsRuntimePreference,
|
||||
updatedAt: Math.max(project.updatedAt, existingProject.updatedAt)
|
||||
}
|
||||
: project
|
||||
})
|
||||
return {
|
||||
projects: [...projectedProjects, ...independentProjects],
|
||||
projects: [...succession.projects, ...independentProjects],
|
||||
projectHostSetups: [...projection.setups, ...independentSetups]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import type { DetectedPort, EnrichedDetectedPort, PortForwardEntry } from '../../shared/ssh-types'
|
||||
import type { Store } from '../persistence'
|
||||
import { splitWorktreeId } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId } from '../../shared/worktree/id'
|
||||
import {
|
||||
advertisedUrlWatcher,
|
||||
type AdvertisedUrl,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'node:path'
|
||||
import type { Store } from '../persistence'
|
||||
import { splitWorktreeId, splitWorktreeIdForFilesystem } from '../../shared/worktree-id'
|
||||
import { splitWorktreeId, splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
|
||||
import { isFolderRepo } from '../../shared/repo-kind'
|
||||
import type {
|
||||
WorkspacePortKillRequest,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user