fix(projects): a manual folder-to-Git change proves the folder is the Git root first (#29282)

* fix(projects): refuse a folder-to-Git type change that would hide folder workspaces (STA-10638)

A folder project's extra workspaces exist only as metadata rows that the folder listing shows. Changing the project to Git switches it to `git worktree list`, so those workspaces drop out of the next refresh and their saved tabs are purged as if they had been deleted. Only the automatic upgrade checked for this; `orca project setup-update --kind git`, repo updates and clone-into-folder did not.

The check now lives in the one repo write every type change goes through, so any caller gets the same refusal and nothing is written. The automatic upgrade and both clone paths use the same check and keep the project as a folder project.

* test(repos): give the repo handler store mock the metadata read the clone path now uses

* fix(projects): a manual folder-to-Git change proves the folder is the Git root first (STA-10638)

Only the automatic upgrade checked that a folder project's path is the top of a Git repository and recorded Git's spelling of that root. A manual change (orca project setup-update --kind git, repo updates, clone into a folder project) flipped the type without either, so a project inside another repo became a broken Git project, and a project whose path Git spells differently lost its main workspace's id and tabs on the next refresh. All of them now run the same proof and write the proven root with the type change. SSH projects keep the unverified change because their Git runs on a host this process can't probe.

* Revalidate folder-to-Git conversion targets after Git proof

* Explain when a project changes during its Git check

* Fix clone conversion for replaced folder project

* test(runtime): create real Git root for clone upgrade fixture

* test: normalize runtime clone root stamp expectation

* test: check clone registration identity without unknown access

* chore: restore main's pnpm-lock.yaml that the main merge reverted
This commit is contained in:
Brennan Benson
2026-10-16 15:53:12 -07:00
committed by GitHub
parent c31712f1a0
commit fa923c67e3
14 changed files with 436 additions and 91 deletions
@@ -28,6 +28,7 @@ import {
} from '../providers/ssh-host-not-served-error'
import { runWorktreeChangeInvalidators } from '../ipc/worktree-change-invalidators'
import { prepareLocalWorktreeRootForRepo } from '../worktree-root-preparation'
import { updateProjectHostSetupWithProvenKind } from '../folder-repo-git-conversion'
import type { RuntimeStore } from './runtime-store-contract'
import { readAllWorktreeMetaForRepo } from '../persistence/host-qualified-worktree-meta'
import {
@@ -142,13 +143,31 @@ export class RuntimeProjectHostSetupController {
)
}
updateSetup(args: ProjectHostSetupUpdateArgs): ProjectHostSetupUpdateResult {
// Why sync up front: availability and the WSL move fence refuse before any Git probe starts.
updateSetup(args: ProjectHostSetupUpdateArgs): Promise<ProjectHostSetupUpdateResult> {
const store = this.deps.getStore()
if (!store?.updateProjectHostSetup) {
const updateProjectHostSetup = store?.updateProjectHostSetup?.bind(store)
if (!store || !updateProjectHostSetup) {
throw new Error('runtime_unavailable')
}
assertWslMoveHostSetupIdWritable(store, args.setupId)
const result = store.updateProjectHostSetup(args)
return this.finishSetupUpdate(store, updateProjectHostSetup, args)
}
private async finishSetupUpdate(
store: RuntimeStore,
updateProjectHostSetup: NonNullable<RuntimeStore['updateProjectHostSetup']>,
args: ProjectHostSetupUpdateArgs
): Promise<ProjectHostSetupUpdateResult> {
const result = await updateProjectHostSetupWithProvenKind(
store,
(setupArgs) => {
assertWslMoveHostSetupIdWritable(store, setupArgs.setupId)
return updateProjectHostSetup(setupArgs)
},
args,
true
)
if (!result) {
throw new Error(`Project host setup not found: ${args.setupId}`)
}