mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 00:02:19 +00:00
309 lines
11 KiB
TypeScript
309 lines
11 KiB
TypeScript
import type {
|
|
GitBranchCompareResult,
|
|
GitConflictOperation,
|
|
GitDiffResult,
|
|
GitPushTarget,
|
|
GitStatusResult,
|
|
GitUpstreamStatus,
|
|
GitWorktreeInfo,
|
|
Worktree
|
|
} from '../../shared/types'
|
|
import { getRemoteFileUrl } from '../git/repo'
|
|
import {
|
|
bulkStageFiles,
|
|
bulkUnstageFiles,
|
|
commitChanges,
|
|
detectConflictOperation,
|
|
discardChanges,
|
|
getBranchCompare,
|
|
getBranchDiff,
|
|
getDiff,
|
|
getStatus as getGitStatus,
|
|
stageFile,
|
|
unstageFile
|
|
} from '../git/status'
|
|
import { getUpstreamStatus } from '../git/upstream'
|
|
import { gitFetch, gitPull, gitPush } from '../git/remote'
|
|
import { getSshGitProvider } from '../providers/ssh-git-dispatch'
|
|
import { normalizeRuntimeRelativePath } from './runtime-relative-paths'
|
|
|
|
export type ResolvedRuntimeGitWorktree = Worktree & { git: GitWorktreeInfo }
|
|
|
|
export type RuntimeGitCommandHost = {
|
|
resolveRuntimeGitTarget(
|
|
selector: string
|
|
): Promise<{ worktree: ResolvedRuntimeGitWorktree; connectionId?: string }>
|
|
}
|
|
|
|
export class RuntimeGitCommands {
|
|
constructor(private readonly host: RuntimeGitCommandHost) {}
|
|
|
|
async getRuntimeGitStatus(worktreeSelector: string): Promise<GitStatusResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
return provider.getStatus(target.worktree.path)
|
|
}
|
|
return getGitStatus(target.worktree.path)
|
|
}
|
|
|
|
async getRuntimeGitConflictOperation(worktreeSelector: string): Promise<GitConflictOperation> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
return provider.detectConflictOperation(target.worktree.path)
|
|
}
|
|
return detectConflictOperation(target.worktree.path)
|
|
}
|
|
|
|
async getRuntimeGitDiff(
|
|
worktreeSelector: string,
|
|
filePath: string,
|
|
staged: boolean,
|
|
compareAgainstHead?: boolean
|
|
): Promise<GitDiffResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeRelativePath(filePath)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
return provider.getDiff(target.worktree.path, relativePath, staged, compareAgainstHead)
|
|
}
|
|
return getDiff(target.worktree.path, relativePath, staged, compareAgainstHead)
|
|
}
|
|
|
|
async getRuntimeGitBranchCompare(
|
|
worktreeSelector: string,
|
|
baseRef: string
|
|
): Promise<GitBranchCompareResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
return provider.getBranchCompare(target.worktree.path, baseRef)
|
|
}
|
|
return getBranchCompare(target.worktree.path, baseRef)
|
|
}
|
|
|
|
async getRuntimeGitUpstreamStatus(worktreeSelector: string): Promise<GitUpstreamStatus> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
return provider.getUpstreamStatus(target.worktree.path)
|
|
}
|
|
return getUpstreamStatus(target.worktree.path)
|
|
}
|
|
|
|
async fetchRuntimeGit(worktreeSelector: string): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.fetchRemote(target.worktree.path)
|
|
return { ok: true }
|
|
}
|
|
await gitFetch(target.worktree.path)
|
|
return { ok: true }
|
|
}
|
|
|
|
async pullRuntimeGit(worktreeSelector: string): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.pullBranch(target.worktree.path)
|
|
return { ok: true }
|
|
}
|
|
await gitPull(target.worktree.path)
|
|
return { ok: true }
|
|
}
|
|
|
|
async pushRuntimeGit(
|
|
worktreeSelector: string,
|
|
publish?: boolean,
|
|
pushTarget?: GitPushTarget
|
|
): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.pushBranch(target.worktree.path, publish === true, pushTarget)
|
|
return { ok: true }
|
|
}
|
|
await gitPush(target.worktree.path, publish === true, pushTarget)
|
|
return { ok: true }
|
|
}
|
|
|
|
async getRuntimeGitBranchDiff(
|
|
worktreeSelector: string,
|
|
compare: { mergeBase: string; headOid: string },
|
|
filePath: string,
|
|
oldPath?: string
|
|
): Promise<GitDiffResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeRelativePath(filePath)
|
|
const oldRelativePath = oldPath ? normalizeRuntimeRelativePath(oldPath) : undefined
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
const results = await provider.getBranchDiff(target.worktree.path, compare.mergeBase, {
|
|
includePatch: true,
|
|
filePath: relativePath,
|
|
oldPath: oldRelativePath
|
|
})
|
|
return (
|
|
results[0] ?? {
|
|
kind: 'text',
|
|
originalContent: '',
|
|
modifiedContent: '',
|
|
originalIsBinary: false,
|
|
modifiedIsBinary: false
|
|
}
|
|
)
|
|
}
|
|
return getBranchDiff(target.worktree.path, {
|
|
mergeBase: compare.mergeBase,
|
|
headOid: compare.headOid,
|
|
filePath: relativePath,
|
|
oldPath: oldRelativePath
|
|
})
|
|
}
|
|
|
|
async commitRuntimeGit(
|
|
worktreeSelector: string,
|
|
message: string
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
if (message.trim().length === 0) {
|
|
throw new Error('Commit message is required')
|
|
}
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
return provider.commit(target.worktree.path, message)
|
|
}
|
|
return commitChanges(target.worktree.path, message)
|
|
}
|
|
|
|
async stageRuntimeGitPath(worktreeSelector: string, filePath: string): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeRelativePath(filePath)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.stageFile(target.worktree.path, relativePath)
|
|
return { ok: true }
|
|
}
|
|
await stageFile(target.worktree.path, relativePath)
|
|
return { ok: true }
|
|
}
|
|
|
|
async unstageRuntimeGitPath(worktreeSelector: string, filePath: string): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeRelativePath(filePath)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.unstageFile(target.worktree.path, relativePath)
|
|
return { ok: true }
|
|
}
|
|
await unstageFile(target.worktree.path, relativePath)
|
|
return { ok: true }
|
|
}
|
|
|
|
async bulkStageRuntimeGitPaths(
|
|
worktreeSelector: string,
|
|
filePaths: string[]
|
|
): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePaths = filePaths.map((path) => normalizeRuntimeRelativePath(path))
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.bulkStageFiles(target.worktree.path, relativePaths)
|
|
return { ok: true }
|
|
}
|
|
await bulkStageFiles(target.worktree.path, relativePaths)
|
|
return { ok: true }
|
|
}
|
|
|
|
async bulkUnstageRuntimeGitPaths(
|
|
worktreeSelector: string,
|
|
filePaths: string[]
|
|
): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePaths = filePaths.map((path) => normalizeRuntimeRelativePath(path))
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.bulkUnstageFiles(target.worktree.path, relativePaths)
|
|
return { ok: true }
|
|
}
|
|
await bulkUnstageFiles(target.worktree.path, relativePaths)
|
|
return { ok: true }
|
|
}
|
|
|
|
async discardRuntimeGitPath(worktreeSelector: string, filePath: string): Promise<{ ok: true }> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeRelativePath(filePath)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
await provider.discardChanges(target.worktree.path, relativePath)
|
|
return { ok: true }
|
|
}
|
|
await discardChanges(target.worktree.path, relativePath)
|
|
return { ok: true }
|
|
}
|
|
|
|
async getRuntimeGitRemoteFileUrl(
|
|
worktreeSelector: string,
|
|
relativePath: string,
|
|
line: number
|
|
): Promise<string | null> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const normalizedRelativePath = normalizeRuntimeRelativePath(relativePath)
|
|
const provider = target.connectionId ? getSshGitProvider(target.connectionId) : null
|
|
if (target.connectionId) {
|
|
if (!provider) {
|
|
throw new Error('remote_git_unavailable')
|
|
}
|
|
return provider.getRemoteFileUrl(target.worktree.path, normalizedRelativePath, line)
|
|
}
|
|
return getRemoteFileUrl(target.worktree.path, normalizedRelativePath, line)
|
|
}
|
|
}
|