mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 00:03:52 +00:00
253 lines
8.2 KiB
TypeScript
253 lines
8.2 KiB
TypeScript
import {
|
|
resolveSourceControlCommitAreaPrimaryActionDecision,
|
|
type SourceControlCommitAreaPrimaryActionDecision,
|
|
type SourceControlRemoteOpKind
|
|
} from '../../../src/shared/source-control-primary-action-decision'
|
|
import type { MobileGitBranchCompareResult } from './mobile-branch-compare'
|
|
import type { MobileGitStatusResult } from './mobile-git-status'
|
|
|
|
type GitStep = { method: string; params?: Record<string, unknown> }
|
|
|
|
type MobileSourceControlPrimaryActionKind = SourceControlCommitAreaPrimaryActionDecision['kind']
|
|
type MobileSourceControlPrimaryActionDecision = SourceControlCommitAreaPrimaryActionDecision
|
|
type MobileSourceControlRemoteOpKind = SourceControlRemoteOpKind
|
|
|
|
export type MobileSourceControlPrimaryAction = {
|
|
kind: MobileSourceControlPrimaryActionKind
|
|
label: string
|
|
accessibilityLabel: string
|
|
accessibilityHint: string
|
|
disabled: boolean
|
|
loading: boolean
|
|
requiresForceWithLease?: boolean
|
|
onPress: () => void
|
|
}
|
|
|
|
export type MobileSourceControlPrimaryActionHandlers = {
|
|
commit: () => Promise<boolean>
|
|
stageAll: () => Promise<void>
|
|
runActionSheetGitSequence: (actionId: string, steps: GitStep[]) => Promise<void>
|
|
runActionSheetGitSync: () => Promise<void>
|
|
}
|
|
|
|
export type MobileSourceControlPrimaryActionArgs = {
|
|
status: MobileGitStatusResult | null
|
|
hasUnresolvedConflicts: boolean
|
|
stageablePaths: readonly string[]
|
|
stagedCount: number
|
|
unstagedCount: number
|
|
commitMessage: string
|
|
busyAction: string | null
|
|
openingPath: string | null
|
|
openingBranchPath: string | null
|
|
branchCompareResult: MobileGitBranchCompareResult | null
|
|
handlers: MobileSourceControlPrimaryActionHandlers
|
|
}
|
|
|
|
export function buildMobileSourceControlPrimaryAction(
|
|
args: MobileSourceControlPrimaryActionArgs
|
|
): MobileSourceControlPrimaryAction {
|
|
const decision = resolveSourceControlCommitAreaPrimaryActionDecision({
|
|
stagedCount: args.stagedCount,
|
|
hasUnstagedChanges: args.unstagedCount > 0,
|
|
hasStageableChanges: args.stageablePaths.length > 0,
|
|
// Why: the commit-area decision keeps the desktop input shape, but partial
|
|
// staging only matters to commit eligibility/dropdowns. Avoid an extra entry scan.
|
|
hasPartiallyStagedChanges: false,
|
|
hasMessage: args.commitMessage.trim().length > 0,
|
|
hasUnresolvedConflicts: args.hasUnresolvedConflicts,
|
|
isCommitting: args.busyAction === 'commit',
|
|
isRemoteOperationActive: isMobileRemoteOperationActive(args.busyAction),
|
|
inFlightRemoteOpKind: getInFlightRemoteOpKind(args.busyAction),
|
|
upstreamStatus: args.status?.upstreamStatus,
|
|
branchCommitsAhead: getMobileBranchCommitsAhead(args),
|
|
hasCurrentBranch: Boolean(args.status?.branch)
|
|
})
|
|
const ioBusy =
|
|
args.busyAction !== null || args.openingPath !== null || args.openingBranchPath !== null
|
|
const disabled = decision.disabled || ioBusy
|
|
|
|
return {
|
|
kind: decision.kind,
|
|
label: getMobilePrimaryActionLabel(decision),
|
|
accessibilityLabel: getMobilePrimaryActionLabel(decision),
|
|
accessibilityHint: getMobilePrimaryActionHint(decision),
|
|
disabled,
|
|
loading: isLoadingDecision(decision, args.busyAction),
|
|
requiresForceWithLease: decision.requiresForceWithLease,
|
|
onPress: () => {
|
|
if (disabled) {
|
|
return
|
|
}
|
|
void runMobilePrimaryAction(decision, args.handlers)
|
|
}
|
|
}
|
|
}
|
|
|
|
function isMobileRemoteOperationActive(busyAction: string | null): boolean {
|
|
return getInFlightRemoteOpKind(busyAction) !== null
|
|
}
|
|
|
|
function getInFlightRemoteOpKind(
|
|
busyAction: string | null
|
|
): MobileSourceControlRemoteOpKind | null {
|
|
switch (busyAction) {
|
|
case 'push':
|
|
case 'commit-push':
|
|
case 'push-create-pr':
|
|
return 'push'
|
|
case 'force-push':
|
|
return 'force_push'
|
|
case 'pull':
|
|
return 'pull'
|
|
case 'sync':
|
|
case 'commit-sync':
|
|
return 'sync'
|
|
case 'fetch':
|
|
return 'fetch'
|
|
case 'publish':
|
|
return 'publish'
|
|
case 'fast-forward':
|
|
return 'fast_forward'
|
|
case 'rebase':
|
|
return 'rebase'
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
function getMobileBranchCommitsAhead(
|
|
args: MobileSourceControlPrimaryActionArgs
|
|
): number | undefined {
|
|
const summary = args.branchCompareResult?.summary
|
|
if (summary?.status === 'ready' && summary.commitsAhead !== undefined) {
|
|
return summary.commitsAhead
|
|
}
|
|
const upstream = args.status?.upstreamStatus
|
|
return upstream?.hasUpstream ? upstream.ahead : undefined
|
|
}
|
|
|
|
function getMobilePrimaryActionLabel(decision: MobileSourceControlPrimaryActionDecision): string {
|
|
if (decision.requiresForceWithLease) {
|
|
return 'Force Push'
|
|
}
|
|
switch (decision.kind) {
|
|
case 'commit':
|
|
return 'Commit'
|
|
case 'stage':
|
|
return 'Stage All'
|
|
case 'push':
|
|
return 'Push'
|
|
case 'pull':
|
|
return 'Pull'
|
|
case 'sync':
|
|
return 'Sync'
|
|
case 'publish':
|
|
return 'Publish Branch'
|
|
}
|
|
}
|
|
|
|
function getMobilePrimaryActionHint(decision: MobileSourceControlPrimaryActionDecision): string {
|
|
switch (decision.titleIntent) {
|
|
case 'commit_in_progress':
|
|
return 'Commit in progress.'
|
|
case 'force_push_in_progress':
|
|
return 'Force push in progress.'
|
|
case 'action_in_progress':
|
|
case 'remote_operation_in_progress':
|
|
return 'Remote operation in progress.'
|
|
case 'remote_operation_blocks_commit':
|
|
return 'Try again once the remote operation finishes.'
|
|
case 'resolve_conflicts_before_commit':
|
|
return 'Resolve conflicts before committing.'
|
|
case 'commit_staged_changes':
|
|
return 'Commit staged changes.'
|
|
case 'enter_commit_message':
|
|
return 'Enter a commit message to commit.'
|
|
case 'stage_all_changes':
|
|
return 'Stage all changes.'
|
|
case 'stage_file_to_commit':
|
|
return 'Stage at least one file to commit.'
|
|
case 'checkout_branch_before_publish':
|
|
return 'Check out a branch before publishing commits.'
|
|
case 'checking_review_status':
|
|
return 'Checking review status.'
|
|
case 'review_already_merged':
|
|
return 'Nothing to commit. The review is already merged.'
|
|
case 'publish_branch':
|
|
return 'Publish this branch to origin.'
|
|
case 'push_linked_review':
|
|
return 'Push updates to the linked review branch.'
|
|
case 'linked_review_target_unavailable':
|
|
return 'The linked review branch is unavailable.'
|
|
case 'force_push_with_lease':
|
|
return 'Force push with lease to update the remote branch.'
|
|
case 'sync_counts':
|
|
return `Pull ${decision.behind ?? 0}, push ${decision.ahead ?? 0}.`
|
|
case 'pull_count':
|
|
return `Pull ${decision.count ?? 0} commit${decision.count === 1 ? '' : 's'}.`
|
|
case 'push_count':
|
|
return `Push ${decision.count ?? 0} commit${decision.count === 1 ? '' : 's'}.`
|
|
case 'nothing_to_commit_up_to_date':
|
|
return 'Nothing to commit. Branch is up to date.'
|
|
}
|
|
}
|
|
|
|
function isLoadingDecision(
|
|
decision: MobileSourceControlPrimaryActionDecision,
|
|
busyAction: string | null
|
|
): boolean {
|
|
switch (decision.kind) {
|
|
case 'commit':
|
|
return busyAction === 'commit'
|
|
case 'stage':
|
|
return busyAction === 'stage-all'
|
|
case 'push':
|
|
return (
|
|
busyAction === 'push' ||
|
|
busyAction === 'force-push' ||
|
|
busyAction === 'commit-push' ||
|
|
busyAction === 'push-create-pr'
|
|
)
|
|
case 'pull':
|
|
return busyAction === 'pull'
|
|
case 'sync':
|
|
return busyAction === 'sync' || busyAction === 'commit-sync'
|
|
case 'publish':
|
|
return busyAction === 'publish'
|
|
}
|
|
}
|
|
|
|
async function runMobilePrimaryAction(
|
|
decision: MobileSourceControlPrimaryActionDecision,
|
|
handlers: MobileSourceControlPrimaryActionHandlers
|
|
): Promise<void> {
|
|
switch (decision.kind) {
|
|
case 'commit':
|
|
await handlers.commit()
|
|
return
|
|
case 'stage':
|
|
await handlers.stageAll()
|
|
return
|
|
case 'push': {
|
|
const params = decision.requiresForceWithLease ? { forceWithLease: true } : undefined
|
|
await handlers.runActionSheetGitSequence(
|
|
decision.requiresForceWithLease ? 'force-push' : 'push',
|
|
[{ method: 'git.push', params }]
|
|
)
|
|
return
|
|
}
|
|
case 'pull':
|
|
await handlers.runActionSheetGitSequence('pull', [{ method: 'git.pull' }])
|
|
return
|
|
case 'sync':
|
|
await handlers.runActionSheetGitSync()
|
|
return
|
|
case 'publish':
|
|
await handlers.runActionSheetGitSequence('publish', [
|
|
{ method: 'git.push', params: { publish: true } }
|
|
])
|
|
return
|
|
}
|
|
}
|