|
1 | | -import { useCallback, useEffect, useMemo } from "react"; |
| 1 | +import { useCallback, useEffect, useMemo, useState } from "react"; |
2 | 2 | import { Alert } from "react-native"; |
3 | 3 |
|
4 | 4 | import { EnvironmentProject, EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; |
@@ -39,6 +39,9 @@ export function useSelectedThreadGitActions() { |
39 | 39 | const pull = useAtomCommand(vcsEnvironment.pull, { reportFailure: false }); |
40 | 40 | const { selectedThread, selectedThreadProject } = useThreadSelection(); |
41 | 41 | const { selectedThreadCwd, selectedThreadWorktreePath } = useSelectedThreadWorktree(); |
| 42 | + const [pendingWorkspaceMetadataCleanup, setPendingWorkspaceMetadataCleanup] = useState< |
| 43 | + string | null |
| 44 | + >(null); |
42 | 45 | const runStackedAction = useAtomCommand( |
43 | 46 | vcsActionManager.runStackedAction({ |
44 | 47 | environmentId: selectedThread?.environmentId ?? null, |
@@ -311,44 +314,80 @@ export function useSelectedThreadGitActions() { |
311 | 314 | const project = selectedThreadProject; |
312 | 315 | const worktreePath = selectedThreadWorktreePath; |
313 | 316 | if (!thread || !project || !worktreePath) { |
314 | | - return; |
| 317 | + return false; |
315 | 318 | } |
316 | | - const confirmed = await new Promise<boolean>((resolve) => { |
317 | | - Alert.alert( |
318 | | - `Remove this ${vcsTerminology.workspaceNoun}?`, |
319 | | - `${worktreePath}\n\nThe thread stays; only the ${vcsTerminology.workspaceNoun} is deleted.`, |
320 | | - [ |
321 | | - { text: "Cancel", style: "cancel", onPress: () => resolve(false) }, |
322 | | - { text: "Remove", style: "destructive", onPress: () => resolve(true) }, |
323 | | - ], |
324 | | - ); |
325 | | - }); |
326 | | - if (!confirmed) { |
327 | | - return; |
328 | | - } |
329 | | - const result = await removeWorktree({ |
330 | | - environmentId: thread.environmentId, |
331 | | - input: { cwd: project.workspaceRoot, path: worktreePath, force: true }, |
332 | | - }); |
333 | | - if (AsyncResult.isFailure(result)) { |
334 | | - const error = Cause.squash(result.cause); |
335 | | - showGitActionResult({ |
336 | | - type: "error", |
337 | | - title: `Failed to remove ${vcsTerminology.workspaceNoun}`, |
338 | | - description: error instanceof Error ? error.message : "An error occurred.", |
| 319 | + if (pendingWorkspaceMetadataCleanup !== worktreePath) { |
| 320 | + const confirmed = await new Promise<boolean>((resolve) => { |
| 321 | + Alert.alert( |
| 322 | + `Remove this ${vcsTerminology.workspaceNoun}?`, |
| 323 | + `${worktreePath}\n\nThe thread stays; only the ${vcsTerminology.workspaceNoun} is deleted.`, |
| 324 | + [ |
| 325 | + { text: "Cancel", style: "cancel", onPress: () => resolve(false) }, |
| 326 | + { text: "Remove", style: "destructive", onPress: () => resolve(true) }, |
| 327 | + ], |
| 328 | + ); |
339 | 329 | }); |
340 | | - return; |
| 330 | + if (!confirmed) return false; |
| 331 | + |
| 332 | + let result = await removeWorktree({ |
| 333 | + environmentId: thread.environmentId, |
| 334 | + input: { cwd: project.workspaceRoot, path: worktreePath, force: false }, |
| 335 | + }); |
| 336 | + if (AsyncResult.isFailure(result)) { |
| 337 | + const error = Cause.squash(result.cause); |
| 338 | + const message = error instanceof Error ? error.message : "An error occurred."; |
| 339 | + if (message.includes("uncommitted or unbookmarked changes")) { |
| 340 | + const discard = await new Promise<boolean>((resolve) => { |
| 341 | + Alert.alert( |
| 342 | + `Discard changes and remove ${vcsTerminology.workspaceNoun}?`, |
| 343 | + `Uncommitted or unbookmarked changes in ${worktreePath} will be lost.`, |
| 344 | + [ |
| 345 | + { text: "Cancel", style: "cancel", onPress: () => resolve(false) }, |
| 346 | + { text: "Discard and remove", style: "destructive", onPress: () => resolve(true) }, |
| 347 | + ], |
| 348 | + ); |
| 349 | + }); |
| 350 | + if (!discard) return false; |
| 351 | + result = await removeWorktree({ |
| 352 | + environmentId: thread.environmentId, |
| 353 | + input: { cwd: project.workspaceRoot, path: worktreePath, force: true }, |
| 354 | + }); |
| 355 | + } |
| 356 | + } |
| 357 | + if (AsyncResult.isFailure(result)) { |
| 358 | + const error = Cause.squash(result.cause); |
| 359 | + showGitActionResult({ |
| 360 | + type: "error", |
| 361 | + title: `Failed to remove ${vcsTerminology.workspaceNoun}`, |
| 362 | + description: error instanceof Error ? error.message : "An error occurred.", |
| 363 | + }); |
| 364 | + return false; |
| 365 | + } |
341 | 366 | } |
342 | | - await syncSelectedThreadBranchState({ |
| 367 | + const syncResult = await syncSelectedThreadBranchState({ |
343 | 368 | thread, |
344 | 369 | cwd: project.workspaceRoot, |
345 | 370 | nextThreadState: { worktreePath: null }, |
346 | 371 | }); |
| 372 | + if (AsyncResult.isFailure(syncResult)) { |
| 373 | + setPendingWorkspaceMetadataCleanup(worktreePath); |
| 374 | + const error = Cause.squash(syncResult.cause); |
| 375 | + showGitActionResult({ |
| 376 | + type: "error", |
| 377 | + title: `${vcsTerminology.workspaceNounTitle} removed, but thread update failed`, |
| 378 | + description: |
| 379 | + error instanceof Error ? error.message : "Try clearing the thread workspace again.", |
| 380 | + }); |
| 381 | + return false; |
| 382 | + } |
| 383 | + setPendingWorkspaceMetadataCleanup(null); |
347 | 384 | showGitActionResult({ |
348 | 385 | type: "success", |
349 | 386 | title: `${vcsTerminology.workspaceNounTitle} removed`, |
350 | 387 | }); |
| 388 | + return true; |
351 | 389 | }, [ |
| 390 | + pendingWorkspaceMetadataCleanup, |
352 | 391 | removeWorktree, |
353 | 392 | selectedThread, |
354 | 393 | selectedThreadProject, |
@@ -446,6 +485,7 @@ export function useSelectedThreadGitActions() { |
446 | 485 | onCreateSelectedThreadBranch, |
447 | 486 | onCreateSelectedThreadWorktree, |
448 | 487 | onRemoveSelectedThreadWorkspace, |
| 488 | + pendingWorkspaceMetadataCleanup: pendingWorkspaceMetadataCleanup === selectedThreadWorktreePath, |
449 | 489 | onPullSelectedThreadBranch, |
450 | 490 | onRunSelectedThreadGitAction, |
451 | 491 | }; |
|
0 commit comments