Uses the selected thinking variant for Git-generated session prompts Preserves current variant for voice-triggered messages
897 lines
31 KiB
TypeScript
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);
|
|
}
|