Files
orca/src/main/runtime/rpc/methods/git.ts
T
JinjingandOrca aeaf0dece4 Create PRs from Source Control (#2478)
* Create PRs directly from Source Control

- Replace the modal flow with an inline PR composer in the sidebar
- Keep PR creation state and validation scoped per worktree
- Rename the recovery action to clarify it only pushes before creating PRs

* Clean up fork PR remotes after worktree deletion

- Track Orca-created push target remotes in worktree metadata
- Reuse ownership markers when later worktrees share the same fork remote
- Fetch only the selected PR base instead of every remote before drafting PRs
- Mirror local branch cleanup for SSH worktree deletion

* Stabilize pull request creation flow

- Keep PR actions and composer fields locked while generation or creation is in flight
- Refresh git status, branch comparison, and history after remote actions settle
- Disable push-only actions on diverged branches so users sync first

* Make PR context generation read-only

- Stop rebasing or probing HEAD before collecting PR draft context
- Allow git operations on known repo roots without refreshing worktree cache

Co-authored-by: Orca <help@stably.ai>

* fix: address review findings

---------

Co-authored-by: Orca <help@stably.ai>
2026-05-20 20:11:38 -07:00

264 lines
8.3 KiB
TypeScript

import { defineMethod, type RpcMethod } from '../core'
import type { GlobalSettings } from '../../../../shared/types'
import {
GitBranchCompare,
GitBranchDiff,
GitBulkPaths,
GitCheckIgnored,
GitCommit,
GitCommitCompare,
GitCommitDiff,
GitDiscoverCommitMessageModels,
GitDiff,
GitFilePath,
GitGenerateCommitMessage,
GitGeneratePullRequestFields,
GitHistory,
GitPush,
GitRemoteFileUrl,
GitStatusParams,
WorktreeSelector
} from './git-params'
export const GIT_METHODS: RpcMethod[] = [
defineMethod({
name: 'git.status',
params: GitStatusParams,
handler: async (params, { runtime }) =>
params.includeIgnored === undefined
? runtime.getRuntimeGitStatus(params.worktree)
: runtime.getRuntimeGitStatus(params.worktree, { includeIgnored: params.includeIgnored })
}),
defineMethod({
name: 'git.checkIgnored',
params: GitCheckIgnored,
handler: async (params, { runtime }) =>
runtime.checkRuntimeGitIgnoredPaths(params.worktree, params.paths)
}),
defineMethod({
name: 'git.history',
params: GitHistory,
handler: async (params, { runtime }) =>
runtime.getRuntimeGitHistory(params.worktree, {
limit: params.limit,
baseRef: params.baseRef
})
}),
defineMethod({
name: 'git.conflictOperation',
params: WorktreeSelector,
handler: async (params, { runtime }) => runtime.getRuntimeGitConflictOperation(params.worktree)
}),
defineMethod({
name: 'git.diff',
params: GitDiff,
handler: async (params, { runtime }) =>
runtime.getRuntimeGitDiff(
params.worktree,
params.filePath,
params.staged,
params.compareAgainstHead
)
}),
defineMethod({
name: 'git.branchCompare',
params: GitBranchCompare,
handler: async (params, { runtime }) =>
runtime.getRuntimeGitBranchCompare(params.worktree, params.baseRef)
}),
defineMethod({
name: 'git.commitCompare',
params: GitCommitCompare,
handler: async (params, { runtime }) =>
runtime.getRuntimeGitCommitCompare(params.worktree, params.commitId)
}),
defineMethod({
name: 'git.upstreamStatus',
params: WorktreeSelector,
handler: async (params, { runtime }) => runtime.getRuntimeGitUpstreamStatus(params.worktree)
}),
defineMethod({
name: 'git.fetch',
params: WorktreeSelector,
handler: async (params, { runtime }) => runtime.fetchRuntimeGit(params.worktree)
}),
defineMethod({
name: 'git.pull',
params: WorktreeSelector,
handler: async (params, { runtime }) => runtime.pullRuntimeGit(params.worktree)
}),
defineMethod({
name: 'git.push',
params: GitPush,
handler: async (params, { runtime }) =>
runtime.pushRuntimeGit(
params.worktree,
params.publish,
params.pushTarget as never,
params.forceWithLease
)
}),
defineMethod({
name: 'git.branchDiff',
params: GitBranchDiff,
handler: async (params, { runtime }) =>
runtime.getRuntimeGitBranchDiff(
params.worktree,
params.compare,
params.filePath,
params.oldPath
)
}),
defineMethod({
name: 'git.commitDiff',
params: GitCommitDiff,
handler: async (params, { runtime }) =>
runtime.getRuntimeGitCommitDiff(params.worktree, {
commitOid: params.commitOid,
parentOid: params.parentOid,
filePath: params.filePath,
oldPath: params.oldPath
})
}),
defineMethod({
name: 'git.commit',
params: GitCommit,
handler: async (params, { runtime }) =>
runtime.commitRuntimeGit(params.worktree, params.message)
}),
defineMethod({
name: 'git.generateCommitMessage',
params: GitGenerateCommitMessage,
handler: async (params, { runtime }) => {
if (
params.commitMessageAi === undefined &&
params.agentCmdOverrides === undefined &&
params.enableGitHubAttribution === undefined &&
params.commitMessageDiscoveryHostKey === undefined
) {
return runtime.generateRuntimeCommitMessage(params.worktree)
}
return runtime.generateRuntimeCommitMessage(params.worktree, {
...(params.commitMessageAi !== undefined
? { commitMessageAi: params.commitMessageAi as GlobalSettings['commitMessageAi'] }
: {}),
...(params.agentCmdOverrides !== undefined
? {
agentCmdOverrides: params.agentCmdOverrides as GlobalSettings['agentCmdOverrides']
}
: {}),
...(params.enableGitHubAttribution !== undefined
? { enableGitHubAttribution: params.enableGitHubAttribution }
: {}),
...(params.commitMessageDiscoveryHostKey !== undefined
? { commitMessageDiscoveryHostKey: params.commitMessageDiscoveryHostKey }
: {})
})
}
}),
defineMethod({
name: 'git.discoverCommitMessageModels',
params: GitDiscoverCommitMessageModels,
handler: async (params, { runtime }) =>
runtime.discoverRuntimeCommitMessageModels(
params.worktree,
params.agentId,
params.agentCmdOverrides !== undefined
? {
agentCmdOverrides: params.agentCmdOverrides as GlobalSettings['agentCmdOverrides']
}
: {}
)
}),
defineMethod({
name: 'git.cancelGenerateCommitMessage',
params: WorktreeSelector,
handler: async (params, { runtime }) =>
runtime.cancelRuntimeGenerateCommitMessage(params.worktree)
}),
defineMethod({
name: 'git.generatePullRequestFields',
params: GitGeneratePullRequestFields,
handler: async (params, { runtime }) => {
const input = {
base: params.base,
title: params.title,
body: params.body,
draft: params.draft
}
if (
params.commitMessageAi === undefined &&
params.agentCmdOverrides === undefined &&
params.enableGitHubAttribution === undefined &&
params.commitMessageDiscoveryHostKey === undefined
) {
return runtime.generateRuntimePullRequestFields(params.worktree, input)
}
return runtime.generateRuntimePullRequestFields(params.worktree, input, {
...(params.commitMessageAi !== undefined
? { commitMessageAi: params.commitMessageAi as GlobalSettings['commitMessageAi'] }
: {}),
...(params.agentCmdOverrides !== undefined
? {
agentCmdOverrides: params.agentCmdOverrides as GlobalSettings['agentCmdOverrides']
}
: {}),
...(params.enableGitHubAttribution !== undefined
? { enableGitHubAttribution: params.enableGitHubAttribution }
: {}),
...(params.commitMessageDiscoveryHostKey !== undefined
? { commitMessageDiscoveryHostKey: params.commitMessageDiscoveryHostKey }
: {})
})
}
}),
defineMethod({
name: 'git.cancelGeneratePullRequestFields',
params: WorktreeSelector,
handler: async (params, { runtime }) =>
runtime.cancelRuntimeGeneratePullRequestFields(params.worktree)
}),
defineMethod({
name: 'git.stage',
params: GitFilePath,
handler: async (params, { runtime }) =>
runtime.stageRuntimeGitPath(params.worktree, params.filePath)
}),
defineMethod({
name: 'git.bulkStage',
params: GitBulkPaths,
handler: async (params, { runtime }) =>
runtime.bulkStageRuntimeGitPaths(params.worktree, params.filePaths)
}),
defineMethod({
name: 'git.unstage',
params: GitFilePath,
handler: async (params, { runtime }) =>
runtime.unstageRuntimeGitPath(params.worktree, params.filePath)
}),
defineMethod({
name: 'git.bulkUnstage',
params: GitBulkPaths,
handler: async (params, { runtime }) =>
runtime.bulkUnstageRuntimeGitPaths(params.worktree, params.filePaths)
}),
defineMethod({
name: 'git.discard',
params: GitFilePath,
handler: async (params, { runtime }) =>
runtime.discardRuntimeGitPath(params.worktree, params.filePath)
}),
defineMethod({
name: 'git.bulkDiscard',
params: GitBulkPaths,
handler: async (params, { runtime }) =>
runtime.bulkDiscardRuntimeGitPaths(params.worktree, params.filePaths)
}),
defineMethod({
name: 'git.remoteFileUrl',
params: GitRemoteFileUrl,
handler: async (params, { runtime }) =>
runtime.getRuntimeGitRemoteFileUrl(params.worktree, params.relativePath, params.line)
})
]