Files
openchamber/packages/ui/src/lib/gitApi.ts
T
Bohdan Triapitsyn 23d732cf9c fix: preserve thinking variant for generated messages
Uses the selected thinking variant for Git-generated session prompts
Preserves current variant for voice-triggered messages
2026-06-02 13:41:19 +03:00

897 lines
31 KiB
TypeScript

import * as gitHttp from './gitApiHttp';
import { opencodeClient } from './opencode/client';
import { renderMagicPrompt } from './magicPrompts';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useContextStore } from '@/stores/contextStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
export type {
GitStatus,
GitDiffResponse,
GetGitDiffOptions,
GitBranchDetails,
GitBranch,
GitCommitResult,
GitPushResult,
GitPullResult,
GitIdentityProfile,
GitIdentityAuthType,
GitIdentitySummary,
GitLogEntry,
GitLogResponse,
GitWorktreeInfo,
CreateGitWorktreePayload,
GitWorktreeCreateResult,
RemoveGitWorktreePayload,
GitWorktreeValidationError,
GitWorktreeValidationResult,
GitDeleteBranchPayload,
GitDeleteRemoteBranchPayload,
GitRemoveRemotePayload,
DiscoveredGitCredential,
GitRemote,
GitMergeResult,
GitRebaseResult,
MergeConflictDetails,
CommitFileDiffResponse,
} from './api/types';
const getRuntimeGit = () => {
return getRegisteredRuntimeAPIs()?.git ?? null;
};
const requestChatForceScrollBottom = (sessionId: string) => {
if (typeof window === 'undefined') return;
window.dispatchEvent(new CustomEvent('openchamber:chat-force-scroll-bottom', {
detail: { sessionId },
}));
};
const extractJsonObject = (value: string): Record<string, unknown> | null => {
const text = value.trim();
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
const candidate = (fenced?.[1] ?? text).trim();
const starts = [candidate.indexOf('{')].filter((index) => index >= 0);
for (const start of starts) {
for (let end = candidate.length; end > start; end -= 1) {
if (candidate[end - 1] !== '}') continue;
try {
const parsed = JSON.parse(candidate.slice(start, end)) as unknown;
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>;
}
} catch {
// Keep scanning; models sometimes wrap JSON with prose or fences.
}
}
}
return null;
};
const extractAssistantText = (response: unknown): string => {
const data = (response as { data?: { parts?: Array<unknown> } } | null)?.data;
const parts = Array.isArray(data?.parts) ? data.parts : [];
return parts
.map((part) => {
const item = part as { type?: unknown; text?: unknown; content?: unknown; value?: unknown };
if (item.type !== 'text') return '';
if (typeof item.text === 'string') return item.text;
if (typeof item.content === 'string') return item.content;
if (typeof item.value === 'string') return item.value;
return '';
})
.filter((text) => text.trim().length > 0)
.join('\n')
.trim();
};
export async function checkIsGitRepository(directory: string): Promise<boolean> {
const runtime = getRuntimeGit();
if (runtime) return runtime.checkIsGitRepository(directory);
return gitHttp.checkIsGitRepository(directory);
}
export async function getGitStatus(directory: string, options?: { mode?: 'light' }): Promise<import('./api/types').GitStatus> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getGitStatus(directory, options);
return gitHttp.getGitStatus(directory, options);
}
export async function getGitDiff(directory: string, options: import('./api/types').GetGitDiffOptions): Promise<import('./api/types').GitDiffResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getGitDiff(directory, options);
return gitHttp.getGitDiff(directory, options);
}
export async function getGitFileDiff(
directory: string,
options: import('./api/types').GetGitFileDiffOptions
): Promise<import('./api/types').GitFileDiffResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getGitFileDiff(directory, options);
return gitHttp.getGitFileDiff(directory, options);
}
export async function revertGitFile(
directory: string,
filePath: string,
options?: { scope?: 'all' | 'working' }
): Promise<void> {
const runtime = getRuntimeGit();
if (runtime) return runtime.revertGitFile(directory, filePath, options);
return gitHttp.revertGitFile(directory, filePath, options);
}
export async function stageGitFile(directory: string, filePath: string): Promise<void> {
const runtime = getRuntimeGit();
if (runtime?.stageGitFile) return runtime.stageGitFile(directory, filePath);
return gitHttp.stageGitFile(directory, filePath);
}
export async function stageGitFiles(directory: string, filePaths: string[]): Promise<void> {
const runtime = getRuntimeGit();
if (runtime?.stageGitFiles) return runtime.stageGitFiles(directory, filePaths);
return gitHttp.stageGitFiles(directory, filePaths);
}
export async function unstageGitFile(directory: string, filePath: string): Promise<void> {
const runtime = getRuntimeGit();
if (runtime?.unstageGitFile) return runtime.unstageGitFile(directory, filePath);
return gitHttp.unstageGitFile(directory, filePath);
}
export async function unstageGitFiles(directory: string, filePaths: string[]): Promise<void> {
const runtime = getRuntimeGit();
if (runtime?.unstageGitFiles) return runtime.unstageGitFiles(directory, filePaths);
return gitHttp.unstageGitFiles(directory, filePaths);
}
export async function isLinkedWorktree(directory: string): Promise<boolean> {
const runtime = getRuntimeGit();
if (runtime) return runtime.isLinkedWorktree(directory);
return gitHttp.isLinkedWorktree(directory);
}
export async function getGitBranches(directory: string): Promise<import('./api/types').GitBranch> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getGitBranches(directory);
return gitHttp.getGitBranches(directory);
}
export async function deleteGitBranch(directory: string, payload: import('./api/types').GitDeleteBranchPayload): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.deleteGitBranch(directory, payload);
return gitHttp.deleteGitBranch(directory, payload);
}
export async function deleteRemoteBranch(directory: string, payload: import('./api/types').GitDeleteRemoteBranchPayload): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.deleteRemoteBranch(directory, payload);
return gitHttp.deleteRemoteBranch(directory, payload);
}
export async function generateCommitMessage(
directory: string,
files: string[],
options?: { zenModel?: string; providerId?: string; modelId?: string }
): Promise<{ message: import('./api/types').GeneratedCommitMessage }> {
const startedAt = Date.now();
void options;
const generationSession = resolveSessionGenerationContext();
if (!generationSession) {
throw new Error('Select existing session for generation');
}
console.info('[git-generation][browser] request', {
transport: 'session',
kind: 'commit',
directory,
selectedFiles: files.length,
sessionId: generationSession.sessionId,
providerId: generationSession.providerID,
modelId: generationSession.modelID,
agent: generationSession.agent,
});
const visiblePrompt = await renderMagicPrompt('git.commit.generate.visible');
const hiddenPrompt = await renderMagicPrompt('git.commit.generate.instructions', {
selected_files: files.map((file) => `- ${file}`).join('\n'),
});
try {
const structured = await runStructuredGenerationInActiveSession({
directory,
visiblePrompt,
hiddenPrompt,
generationSession,
kind: 'commit',
});
const subject = typeof structured.subject === 'string' ? structured.subject.trim() : '';
const highlights = Array.isArray(structured.highlights)
? structured.highlights.filter((item) => typeof item === 'string').map((item) => item.trim()).filter(Boolean).slice(0, 3)
: [];
if (!subject) {
throw new Error('Structured output missing subject');
}
const result = { message: { subject, highlights } };
console.info('[git-generation][browser] success', {
transport: 'session',
kind: 'commit',
elapsedMs: Date.now() - startedAt,
subjectLength: result.message.subject.length,
highlightsCount: result.message.highlights.length,
});
return result;
} catch (error) {
console.error('[git-generation][browser] failed', {
transport: 'session',
kind: 'commit',
elapsedMs: Date.now() - startedAt,
message: error instanceof Error ? error.message : String(error),
error,
});
throw error;
}
}
export async function generatePullRequestDescription(
directory: string,
payload: { base: string; head: string; context?: string; zenModel?: string; providerId?: string; modelId?: string }
): Promise<import('./api/types').GeneratedPullRequestDescription> {
const startedAt = Date.now();
const generationSession = resolveSessionGenerationContext();
if (!generationSession) {
throw new Error('Select existing session for generation');
}
const commitLog = await getGitLog(directory, {
from: payload.base,
to: payload.head,
maxCount: 50,
});
const commits = (Array.isArray(commitLog?.all) ? commitLog.all : [])
.filter((entry) => typeof entry?.hash === 'string' && entry.hash.length > 0)
.map((entry) => ({
hash: entry.hash,
subject: typeof entry.message === 'string' ? entry.message.trim() : '',
}));
if (commits.length === 0) {
throw new Error(`No commits found in range ${payload.base}...${payload.head}`);
}
const filesSet = new Set<string>();
await Promise.all(commits.map(async (commit) => {
try {
const response = await getCommitFiles(directory, commit.hash);
const files = Array.isArray(response?.files) ? response.files : [];
for (const file of files) {
if (typeof file?.path === 'string' && file.path.trim().length > 0) {
filesSet.add(file.path.trim());
}
}
} catch (error) {
console.warn('[git-generation][browser] failed to collect commit files', {
hash: commit.hash,
error: error instanceof Error ? error.message : String(error),
});
}
}));
const changedFiles = Array.from(filesSet).sort().slice(0, 300);
console.info('[git-generation][browser] request', {
transport: 'session',
kind: 'pr',
directory,
sessionId: generationSession.sessionId,
providerId: generationSession.providerID,
modelId: generationSession.modelID,
agent: generationSession.agent,
base: payload.base,
head: payload.head,
commits: commits.length,
changedFiles: changedFiles.length,
});
const visiblePrompt = await renderMagicPrompt('git.pr.generate.visible');
const hiddenPrompt = await renderMagicPrompt('git.pr.generate.instructions', {
base_branch: payload.base,
head_branch: payload.head,
commits: commits.map((commit) => `- ${commit.hash.slice(0, 7)} ${commit.subject || '(no subject)'}`).join('\n'),
changed_files: changedFiles.length > 0 ? changedFiles.map((file) => `- ${file}`).join('\n') : '- none detected',
additional_context_block: payload.context?.trim() ? `\nAdditional context:\n${payload.context.trim()}` : '',
});
try {
const structured = await runStructuredGenerationInActiveSession({
directory,
visiblePrompt,
hiddenPrompt,
generationSession,
kind: 'pr',
});
const result = {
title: typeof structured.title === 'string' ? structured.title.trim() : '',
body: typeof structured.body === 'string' ? structured.body.trim() : '',
};
console.info('[git-generation][browser] success', {
transport: 'session',
kind: 'pr',
elapsedMs: Date.now() - startedAt,
titleLength: result.title.length,
bodyLength: result.body.length,
});
return result;
} catch (error) {
console.error('[git-generation][browser] failed', {
transport: 'session',
kind: 'pr',
elapsedMs: Date.now() - startedAt,
message: error instanceof Error ? error.message : String(error),
error,
});
throw error;
}
}
type SessionGenerationContext = {
sessionId: string;
providerID: string;
modelID: string;
agent?: string;
variant?: string;
};
const resolveSessionGenerationContext = (): SessionGenerationContext | null => {
const sessionId = useSessionUIStore.getState().currentSessionId;
if (!sessionId) {
return null;
}
const context = useContextStore.getState();
const config = useConfigStore.getState();
const agent = context.getSessionAgentSelection(sessionId) || config.currentAgentName || undefined;
const sessionModel = context.getSessionModelSelection(sessionId);
const agentModel = agent ? context.getAgentModelForSession(sessionId, agent) : null;
const selectedModel = agentModel || sessionModel || (config.currentProviderId && config.currentModelId
? { providerId: config.currentProviderId, modelId: config.currentModelId }
: null);
if (!selectedModel?.providerId || !selectedModel?.modelId) {
return null;
}
const agentVariant = agent
? context.getAgentModelVariantForSession(sessionId, agent, selectedModel.providerId, selectedModel.modelId)
: undefined;
const variant = agentVariant || config.currentVariant || undefined;
return {
sessionId,
providerID: selectedModel.providerId,
modelID: selectedModel.modelId,
agent,
variant,
};
};
const runStructuredGenerationInActiveSession = async ({
directory,
visiblePrompt,
hiddenPrompt,
generationSession,
kind,
}: {
directory: string;
visiblePrompt: string;
hiddenPrompt?: string;
generationSession: SessionGenerationContext;
kind: 'commit' | 'pr';
}): Promise<Record<string, unknown>> => {
const requestStartedAt = Date.now();
console.info('[git-generation][browser] runStructuredGenerationInActiveSession start', {
kind,
directory,
sessionId: generationSession.sessionId,
providerID: generationSession.providerID,
modelID: generationSession.modelID,
agent: generationSession.agent,
variant: generationSession.variant,
});
const trimmedDirectory = typeof directory === 'string' ? directory.trim() : '';
const visiblePromptText = typeof visiblePrompt === 'string' ? visiblePrompt.trim() : '';
const hiddenPromptText = typeof hiddenPrompt === 'string' ? hiddenPrompt.trim() : '';
const promptParts: Array<{ type: 'text'; text: string; synthetic?: boolean }> = [];
if (visiblePromptText) {
promptParts.push({
type: 'text',
text: hiddenPromptText ? `${visiblePromptText}\n\n` : visiblePromptText,
synthetic: false,
});
}
if (hiddenPromptText) {
promptParts.push({ type: 'text', text: hiddenPromptText, synthetic: true });
}
if (promptParts.length === 0) {
throw new Error('Generation prompts are empty');
}
requestChatForceScrollBottom(generationSession.sessionId);
const response = await opencodeClient.withDirectory(directory, async () => {
return opencodeClient.getApiClient().session.prompt({
sessionID: generationSession.sessionId,
...(trimmedDirectory.length > 0 ? { directory: trimmedDirectory } : {}),
model: {
providerID: generationSession.providerID,
modelID: generationSession.modelID,
},
...(generationSession.agent ? { agent: generationSession.agent } : {}),
...(generationSession.variant ? { variant: generationSession.variant } : {}),
parts: promptParts,
});
});
const responseError = response?.error as { message?: string } | undefined;
if (!response?.data) {
throw new Error(responseError?.message || `Failed to generate ${kind} output`);
}
const info = response.data.info as { finish?: string; error?: unknown };
const assistantText = extractAssistantText(response);
const parsedOutput = extractJsonObject(assistantText);
if (!parsedOutput) {
console.error('[git-generation][browser] invalid JSON output', {
kind,
sessionId: generationSession.sessionId,
elapsedMs: Date.now() - requestStartedAt,
finish: info?.finish,
assistantText,
messageInfo: response.data.info,
messageParts: response.data.parts,
});
throw new Error('No JSON output returned by session');
}
return parsedOutput;
};
export async function listGitWorktrees(directory: string): Promise<import('./api/types').GitWorktreeInfo[]> {
const runtime = getRuntimeGit();
if (runtime?.worktree?.list) {
return runtime.worktree.list(directory);
}
if (runtime) return runtime.listGitWorktrees(directory);
return gitHttp.listGitWorktrees(directory);
}
export async function validateGitWorktree(
directory: string,
payload: import('./api/types').CreateGitWorktreePayload
): Promise<import('./api/types').GitWorktreeValidationResult> {
const runtime = getRuntimeGit();
if (runtime?.worktree?.validate) {
return runtime.worktree.validate(directory, payload);
}
if (runtime?.validateGitWorktree) {
return runtime.validateGitWorktree(directory, payload);
}
return gitHttp.validateGitWorktree(directory, payload);
}
export async function getGitWorktreeBootstrapStatus(
directory: string,
): Promise<import('./api/types').GitWorktreeBootstrapStatus> {
const runtime = getRuntimeGit();
if (runtime?.worktree?.bootstrapStatus) {
return runtime.worktree.bootstrapStatus(directory);
}
if (runtime?.getGitWorktreeBootstrapStatus) {
return runtime.getGitWorktreeBootstrapStatus(directory);
}
return gitHttp.getGitWorktreeBootstrapStatus(directory);
}
export async function previewGitWorktree(
directory: string,
payload: import('./api/types').CreateGitWorktreePayload
): Promise<import('./api/types').GitWorktreeCreateResult> {
const runtime = getRuntimeGit();
if (runtime?.worktree?.preview) {
return runtime.worktree.preview(directory, payload);
}
if (runtime?.previewGitWorktree) {
return runtime.previewGitWorktree(directory, payload);
}
return gitHttp.previewGitWorktree(directory, payload);
}
export async function createGitWorktree(
directory: string,
payload: import('./api/types').CreateGitWorktreePayload
): Promise<import('./api/types').GitWorktreeCreateResult> {
const runtime = getRuntimeGit();
if (runtime?.worktree?.create) {
return runtime.worktree.create(directory, payload);
}
if (runtime?.createGitWorktree) {
return runtime.createGitWorktree(directory, payload);
}
return gitHttp.createGitWorktree(directory, payload);
}
export async function deleteGitWorktree(
directory: string,
payload: import('./api/types').RemoveGitWorktreePayload
): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime?.worktree?.remove) {
return runtime.worktree.remove(directory, payload);
}
if (runtime?.deleteGitWorktree) {
return runtime.deleteGitWorktree(directory, payload);
}
return gitHttp.deleteGitWorktree(directory, payload);
}
export const git = {
worktree: {
list: listGitWorktrees,
validate: validateGitWorktree,
create: createGitWorktree,
remove: deleteGitWorktree,
},
};
export async function createGitCommit(
directory: string,
message: string,
options: import('./api/types').CreateGitCommitOptions = {}
): Promise<import('./api/types').GitCommitResult> {
const runtime = getRuntimeGit();
if (runtime) return runtime.createGitCommit(directory, message, options);
return gitHttp.createGitCommit(directory, message, options);
}
export async function gitPush(
directory: string,
options: { remote?: string; branch?: string; options?: string[] | Record<string, unknown> } = {}
): Promise<import('./api/types').GitPushResult> {
const runtime = getRuntimeGit();
if (runtime) return runtime.gitPush(directory, options);
return gitHttp.gitPush(directory, options);
}
export async function gitPull(
directory: string,
options: import('./api/types').GitPullOptions = {}
): Promise<import('./api/types').GitPullResult> {
const runtime = getRuntimeGit();
if (runtime) return runtime.gitPull(directory, options);
return gitHttp.gitPull(directory, options);
}
export async function gitFetch(
directory: string,
options: { remote?: string; branch?: string } = {}
): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.gitFetch(directory, options);
return gitHttp.gitFetch(directory, options);
}
export async function listGitStashes(directory: string): Promise<{ stashes: import('./api/types').GitStashEntry[] }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.listGitStashes(directory);
return gitHttp.listGitStashes(directory);
}
export async function countGitStashFiles(directory: string, refs: string[]): Promise<{ counts: Record<string, number> }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.countGitStashFiles(directory, refs);
return gitHttp.countGitStashFiles(directory, refs);
}
export async function stashGitChanges(directory: string, options: { message?: string } = {}): Promise<{ success: boolean; created: boolean; message: string; output: string }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.stashGitChanges(directory, options);
return gitHttp.stashGitChanges(directory, options);
}
export async function applyGitStash(directory: string, options: { ref: string }): Promise<{ success: boolean; ref: string }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.applyGitStash(directory, options);
return gitHttp.applyGitStash(directory, options);
}
export async function popGitStash(directory: string, options: { ref: string }): Promise<{ success: boolean; ref: string }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.popGitStash(directory, options);
return gitHttp.popGitStash(directory, options);
}
export async function dropGitStash(directory: string, options: { ref: string }): Promise<{ success: boolean; ref: string }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.dropGitStash(directory, options);
return gitHttp.dropGitStash(directory, options);
}
export async function checkoutBranch(directory: string, branch: string): Promise<{ success: boolean; branch: string }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.checkoutBranch(directory, branch);
return gitHttp.checkoutBranch(directory, branch);
}
export async function createBranch(
directory: string,
name: string,
startPoint?: string
): Promise<{ success: boolean; branch: string }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.createBranch(directory, name, startPoint);
return gitHttp.createBranch(directory, name, startPoint);
}
export async function renameBranch(
directory: string,
oldName: string,
newName: string
): Promise<{ success: boolean; branch: string }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.renameBranch(directory, oldName, newName);
return gitHttp.renameBranch(directory, oldName, newName);
}
export async function getGitLog(
directory: string,
options: import('./api/types').GitLogOptions = {}
): Promise<import('./api/types').GitLogResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getGitLog(directory, options);
return gitHttp.getGitLog(directory, options);
}
export async function getCommitFiles(
directory: string,
hash: string
): Promise<import('./api/types').GitCommitFilesResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getCommitFiles(directory, hash);
return gitHttp.getCommitFiles(directory, hash);
}
export async function getCommitFileDiff(
directory: string,
hash: string,
filePath: string,
isBinary: boolean
): Promise<import('./api/types').CommitFileDiffResponse> {
const runtime = getRuntimeGit();
if (runtime?.getCommitFileDiff) return runtime.getCommitFileDiff(directory, hash, filePath, isBinary);
return gitHttp.getCommitFileDiff(directory, hash, filePath, isBinary);
}
export async function getGitIdentities(): Promise<import('./api/types').GitIdentityProfile[]> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getGitIdentities();
return gitHttp.getGitIdentities();
}
export async function createGitIdentity(profile: import('./api/types').GitIdentityProfile): Promise<import('./api/types').GitIdentityProfile> {
const runtime = getRuntimeGit();
if (runtime) return runtime.createGitIdentity(profile);
return gitHttp.createGitIdentity(profile);
}
export async function updateGitIdentity(id: string, updates: import('./api/types').GitIdentityProfile): Promise<import('./api/types').GitIdentityProfile> {
const runtime = getRuntimeGit();
if (runtime) return runtime.updateGitIdentity(id, updates);
return gitHttp.updateGitIdentity(id, updates);
}
export async function deleteGitIdentity(id: string): Promise<void> {
const runtime = getRuntimeGit();
if (runtime) return runtime.deleteGitIdentity(id);
return gitHttp.deleteGitIdentity(id);
}
export async function getCurrentGitIdentity(directory: string): Promise<import('./api/types').GitIdentitySummary | null> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getCurrentGitIdentity(directory);
return gitHttp.getCurrentGitIdentity(directory);
}
export async function hasLocalIdentity(directory: string): Promise<boolean> {
const runtime = getRuntimeGit();
if (runtime?.hasLocalIdentity) return runtime.hasLocalIdentity(directory);
return gitHttp.hasLocalIdentity(directory);
}
export async function setGitIdentity(
directory: string,
profileId: string
): Promise<{ success: boolean; profile: import('./api/types').GitIdentityProfile }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.setGitIdentity(directory, profileId);
return gitHttp.setGitIdentity(directory, profileId);
}
export async function discoverGitCredentials(): Promise<import('./api/types').DiscoveredGitCredential[]> {
const runtime = getRuntimeGit();
if (runtime?.discoverGitCredentials) return runtime.discoverGitCredentials();
return gitHttp.discoverGitCredentials();
}
export async function getGlobalGitIdentity(): Promise<import('./api/types').GitIdentitySummary | null> {
const runtime = getRuntimeGit();
if (runtime?.getGlobalGitIdentity) return runtime.getGlobalGitIdentity();
return gitHttp.getGlobalGitIdentity();
}
export async function getRemoteUrl(directory: string, remote?: string): Promise<string | null> {
const runtime = getRuntimeGit();
if (runtime?.getRemoteUrl) return runtime.getRemoteUrl(directory, remote);
return gitHttp.getRemoteUrl(directory, remote);
}
export async function getRemotes(directory: string): Promise<import('./api/types').GitRemote[]> {
const runtime = getRuntimeGit();
if (runtime) return runtime.getRemotes(directory);
return gitHttp.getRemotes(directory);
}
export async function removeRemote(
directory: string,
payload: import('./api/types').GitRemoveRemotePayload
): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.removeRemote(directory, payload);
return gitHttp.removeRemote(directory, payload);
}
export async function rebase(
directory: string,
options: { onto: string }
): Promise<import('./api/types').GitRebaseResult> {
const runtime = getRuntimeGit();
if (runtime) return runtime.rebase(directory, options);
return gitHttp.rebase(directory, options);
}
export async function abortRebase(directory: string): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.abortRebase(directory);
return gitHttp.abortRebase(directory);
}
export async function merge(
directory: string,
options: { branch: string }
): Promise<import('./api/types').GitMergeResult> {
const runtime = getRuntimeGit();
if (runtime) return runtime.merge(directory, options);
return gitHttp.merge(directory, options);
}
export async function checkoutCommit(
directory: string,
hash: string
): Promise<import('./api/types').CheckoutCommitResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.checkoutCommit(directory, hash);
return gitHttp.checkoutCommit(directory, hash);
}
export async function cherryPick(
directory: string,
hash: string
): Promise<import('./api/types').CherryPickResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.cherryPick(directory, hash);
return gitHttp.cherryPick(directory, hash);
}
export async function revertCommit(
directory: string,
hash: string
): Promise<import('./api/types').RevertCommitResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.revertCommit(directory, hash);
return gitHttp.revertCommit(directory, hash);
}
export async function resetToCommit(
directory: string,
hash: string,
mode: 'soft' | 'mixed' | 'hard',
force?: boolean
): Promise<import('./api/types').ResetToCommitResponse> {
const runtime = getRuntimeGit();
if (runtime) return runtime.resetToCommit(directory, hash, mode, force);
return gitHttp.resetToCommit(directory, hash, mode, force);
}
export async function abortMerge(directory: string): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.abortMerge(directory);
return gitHttp.abortMerge(directory);
}
export async function continueRebase(directory: string): Promise<{ success: boolean; conflict: boolean; conflictFiles?: string[] }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.continueRebase(directory);
return gitHttp.continueRebase(directory);
}
export async function continueMerge(directory: string): Promise<{ success: boolean; conflict: boolean; conflictFiles?: string[] }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.continueMerge(directory);
return gitHttp.continueMerge(directory);
}
export async function stash(
directory: string,
options?: { message?: string; includeUntracked?: boolean }
): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.stash(directory, options);
return gitHttp.stash(directory, options);
}
export async function stashPop(directory: string): Promise<{ success: boolean }> {
const runtime = getRuntimeGit();
if (runtime) return runtime.stashPop(directory);
return gitHttp.stashPop(directory);
}
export async function getConflictDetails(directory: string): Promise<import('./api/types').MergeConflictDetails> {
const runtime = getRuntimeGit();
if (runtime?.getConflictDetails) return runtime.getConflictDetails(directory);
return gitHttp.getConflictDetails(directory);
}
export async function validateWorktreeDirectory(
directory: string,
worktreeRoot: string
): Promise<{
valid: boolean;
insideWorktreeRoot: boolean;
resolvedWorktreeRoot: string | null;
resolvedCwd: string | null;
}> {
const runtime = getRuntimeGit();
if (runtime?.validateWorktreeDirectory) {
return runtime.validateWorktreeDirectory(directory, worktreeRoot);
}
return gitHttp.validateWorktreeDirectory(directory, worktreeRoot);
}
export async function canonicalizeWorktreeState(
directory: string
): Promise<{
worktreeRoot: string | null;
cwd: string | null;
branch: string | null;
headState: 'branch' | 'detached' | 'unborn';
worktreeStatus: 'ready' | 'missing' | 'invalid' | 'not-a-repo';
legacy: boolean;
degraded: boolean;
attentionReason?: 'merge' | 'rebase' | 'cherry-pick' | 'revert' | 'bisect' | null;
}> {
const runtime = getRuntimeGit();
if (runtime?.canonicalizeWorktreeState) {
return runtime.canonicalizeWorktreeState(directory);
}
return gitHttp.canonicalizeWorktreeState(directory);
}