This change introduces a new backend command to fetch file blame using git's line-porcelain output and returns structured per-line metadata. The UI adds a BlameDialog that groups lines by commit, highlights uncommitted changes, and styles the dialog to match the updated theme. - Add get_file_blame command and parsing with uncommitted detection - Wire BlameDialog into the explorer file node actions - Add blame UI component and supporting types and styles
402 lines
12 KiB
TypeScript
402 lines
12 KiB
TypeScript
import { invoke } from "@tauri-apps/api/core";
|
|
|
|
import type {
|
|
CommitAiLocalProfile,
|
|
CommitAiProvider,
|
|
CommitAiStatus,
|
|
ConflictFile,
|
|
GitBlameResult,
|
|
GitBranch,
|
|
GitCommit,
|
|
GitCommitComparison,
|
|
GitRepositoryFile,
|
|
GitSearchHit,
|
|
GitStash,
|
|
GitStatus,
|
|
GitTag,
|
|
LocalModelOption,
|
|
PatchApplyAction,
|
|
RepositoryBundle,
|
|
StoredCredential,
|
|
} from "./types";
|
|
|
|
export function openRepository(path: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("open_repository", { path });
|
|
}
|
|
|
|
export function openRepoInExplorer(path: string): Promise<void> {
|
|
return invoke<void>("open_repo_in_explorer", { path });
|
|
}
|
|
|
|
export function openRepositoryFile(path: string, file: string): Promise<void> {
|
|
return invoke<void>("open_repository_file", { path, file });
|
|
}
|
|
|
|
export function openRepositoryBundle(path: string, commitLimit = 100): Promise<RepositoryBundle> {
|
|
return invoke<RepositoryBundle>("open_repository_bundle", { path, commitLimit });
|
|
}
|
|
|
|
export function cloneRepository(
|
|
remoteUrl: string,
|
|
parentPath: string,
|
|
directoryName?: string,
|
|
username?: string,
|
|
password?: string,
|
|
commitLimit = 100,
|
|
): Promise<RepositoryBundle> {
|
|
return invoke<RepositoryBundle>("clone_repository", {
|
|
remoteUrl,
|
|
parentPath,
|
|
directoryName: directoryName?.trim() ? directoryName.trim() : null,
|
|
username: username ?? null,
|
|
password: password ?? null,
|
|
commitLimit,
|
|
});
|
|
}
|
|
|
|
export function getStatus(path: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("get_status", { path });
|
|
}
|
|
|
|
// Sets the taskbar icon badge to ahead + behind + changed status files (0 clears it). Windows only — a no-op on
|
|
// other platforms, since Windows has no native numeric badge to fall back to.
|
|
export function setSyncBadge(ahead: number, behind: number, changes: number): Promise<void> {
|
|
return invoke<void>("set_sync_badge", { ahead, behind, changes });
|
|
}
|
|
|
|
export function listBranches(path: string): Promise<GitBranch[]> {
|
|
return invoke<GitBranch[]>("list_branches", { path });
|
|
}
|
|
|
|
export function listStashes(path: string): Promise<GitStash[]> {
|
|
return invoke<GitStash[]>("list_stashes", { path });
|
|
}
|
|
|
|
export function checkoutBranch(path: string, branch: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("checkout_branch", { path, branch });
|
|
}
|
|
|
|
export function createBranch(
|
|
path: string,
|
|
branch: string,
|
|
startPoint?: string,
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("create_branch", { path, branch, startPoint: startPoint ?? null });
|
|
}
|
|
|
|
export function renameBranch(
|
|
path: string,
|
|
oldBranch: string,
|
|
newBranch: string,
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("rename_branch", { path, oldBranch, newBranch });
|
|
}
|
|
|
|
export function deleteBranch(path: string, branch: string, force = false): Promise<GitStatus> {
|
|
return invoke<GitStatus>("delete_branch", { path, branch, force });
|
|
}
|
|
|
|
export function listTags(path: string): Promise<GitTag[]> {
|
|
return invoke<GitTag[]>("list_tags", { path });
|
|
}
|
|
|
|
export function createTag(
|
|
path: string,
|
|
name: string,
|
|
target?: string,
|
|
message?: string,
|
|
): Promise<GitTag[]> {
|
|
return invoke<GitTag[]>("create_tag", {
|
|
path,
|
|
name,
|
|
target: target?.trim() ? target.trim() : null,
|
|
message: message?.trim() ? message.trim() : null,
|
|
});
|
|
}
|
|
|
|
export function deleteTag(path: string, name: string): Promise<GitTag[]> {
|
|
return invoke<GitTag[]>("delete_tag", { path, name });
|
|
}
|
|
|
|
export function pushTag(
|
|
path: string,
|
|
name: string,
|
|
username?: string,
|
|
password?: string,
|
|
): Promise<void> {
|
|
return invoke<void>("push_tag", { path, name, username: username ?? null, password: password ?? null });
|
|
}
|
|
|
|
export function cherryPickCommit(path: string, commit: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("cherry_pick_commit", { path, commit });
|
|
}
|
|
|
|
export function cherryPickContinue(path: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("cherry_pick_continue", { path });
|
|
}
|
|
|
|
export function cherryPickAbort(path: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("cherry_pick_abort", { path });
|
|
}
|
|
|
|
export function stageFiles(path: string, files: string[]): Promise<GitStatus> {
|
|
return invoke<GitStatus>("stage_files", { path, files });
|
|
}
|
|
|
|
export function unstageFiles(path: string, files: string[]): Promise<GitStatus> {
|
|
return invoke<GitStatus>("unstage_files", { path, files });
|
|
}
|
|
|
|
export function restoreFiles(
|
|
path: string,
|
|
files: string[],
|
|
staged: boolean,
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("restore_files", { path, files, staged });
|
|
}
|
|
|
|
export function getFilePatch(path: string, file: string, staged: boolean): Promise<string> {
|
|
return invoke<string>("get_file_patch", { path, file, staged });
|
|
}
|
|
|
|
export function applyFilePatch(
|
|
path: string,
|
|
file: string,
|
|
patch: string,
|
|
action: PatchApplyAction,
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("apply_file_patch", { path, file, patch, action });
|
|
}
|
|
|
|
export function commit(path: string, message: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("commit", { path, message });
|
|
}
|
|
|
|
export function amendCommit(path: string, message?: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("amend_commit", { path, message: message?.trim() ? message.trim() : null });
|
|
}
|
|
|
|
export function lastCommitMessage(path: string): Promise<string | null> {
|
|
return invoke<string | null>("last_commit_message", { path });
|
|
}
|
|
|
|
export function undoLastCommit(path: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("undo_last_commit", { path });
|
|
}
|
|
|
|
export function stashPush(
|
|
path: string,
|
|
message?: string,
|
|
includeUntracked = true,
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("stash_push", {
|
|
path,
|
|
message: message?.trim() ? message.trim() : null,
|
|
includeUntracked,
|
|
});
|
|
}
|
|
|
|
export function stashApply(path: string, selector: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("stash_apply", { path, selector });
|
|
}
|
|
|
|
export function stashPop(path: string, selector: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("stash_pop", { path, selector });
|
|
}
|
|
|
|
export function stashDrop(path: string, selector: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("stash_drop", { path, selector });
|
|
}
|
|
|
|
export function commitAiStatus(): Promise<CommitAiStatus> {
|
|
return invoke<CommitAiStatus>("commit_ai_status");
|
|
}
|
|
|
|
export function commitAiLoad(modelId: string): Promise<void> {
|
|
return invoke<void>("commit_ai_load", { modelId });
|
|
}
|
|
|
|
export function commitAiLocalModels(): Promise<LocalModelOption[]> {
|
|
return invoke<LocalModelOption[]>("commit_ai_local_models");
|
|
}
|
|
|
|
export interface CommitAiGenerateOptions {
|
|
notes?: string;
|
|
provider: CommitAiProvider;
|
|
localProfile?: CommitAiLocalProfile;
|
|
model?: string;
|
|
apiKey?: string;
|
|
baseUrl?: string;
|
|
}
|
|
|
|
export function commitAiGenerate(path: string, options: CommitAiGenerateOptions): Promise<string> {
|
|
return invoke<string>("commit_ai_generate", {
|
|
path,
|
|
notes: options.notes,
|
|
provider: options.provider,
|
|
localProfile: options.localProfile,
|
|
model: options.model,
|
|
apiKey: options.apiKey,
|
|
baseUrl: options.baseUrl,
|
|
});
|
|
}
|
|
|
|
export function pull(path: string, username?: string, password?: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("pull", { path, username: username ?? null, password: password ?? null });
|
|
}
|
|
|
|
export function fetchRemote(path: string, username?: string, password?: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("fetch", { path, username: username ?? null, password: password ?? null });
|
|
}
|
|
|
|
export function push(path: string, username?: string, password?: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("push", { path, username: username ?? null, password: password ?? null });
|
|
}
|
|
|
|
export function getRemoteUrl(path: string): Promise<string | null> {
|
|
return invoke<string | null>("get_remote_url", { path });
|
|
}
|
|
|
|
export function credLoad(key: string): Promise<StoredCredential | null> {
|
|
return invoke<StoredCredential | null>("cred_load", { key });
|
|
}
|
|
|
|
export function credSave(
|
|
key: string,
|
|
username: string,
|
|
password: string,
|
|
expiresAt: string | null,
|
|
): Promise<void> {
|
|
return invoke<void>("cred_save", { key, username, password, expiresAt });
|
|
}
|
|
|
|
export function credDelete(key: string): Promise<void> {
|
|
return invoke<void>("cred_delete", { key });
|
|
}
|
|
|
|
export function listCommits(path: string, limit = 100): Promise<GitCommit[]> {
|
|
return invoke<GitCommit[]>("list_commits", { path, limit });
|
|
}
|
|
|
|
export function restoreToCommit(path: string, commit: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("restore_to_commit", { path, commit });
|
|
}
|
|
|
|
export function restoreFileFromCommit(
|
|
path: string,
|
|
commit: string,
|
|
file: string,
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("restore_file_from_commit", { path, commit, file });
|
|
}
|
|
|
|
export function mergeBranch(path: string, branch: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("merge_branch", { path, branch });
|
|
}
|
|
|
|
export function rebaseBranch(path: string, branch: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("rebase_branch", { path, branch });
|
|
}
|
|
|
|
export function rebaseContinue(path: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("rebase_continue", { path });
|
|
}
|
|
|
|
export function rebaseAbort(path: string): Promise<GitStatus> {
|
|
return invoke<GitStatus>("rebase_abort", { path });
|
|
}
|
|
|
|
export function listRepositoryFiles(path: string): Promise<GitRepositoryFile[]> {
|
|
return invoke<GitRepositoryFile[]>("list_repository_files", { path });
|
|
}
|
|
|
|
export function listFileHistory(
|
|
path: string,
|
|
file: string,
|
|
limit = 100,
|
|
requestId?: string,
|
|
): Promise<GitCommit[]> {
|
|
return invoke<GitCommit[]>("list_file_history", { path, file, limit, requestId: requestId ?? null });
|
|
}
|
|
|
|
export function cancelFileHistory(requestId: string): Promise<void> {
|
|
return invoke<void>("cancel_file_history", { requestId });
|
|
}
|
|
|
|
export function getFileBlame(path: string, file: string): Promise<GitBlameResult> {
|
|
return invoke<GitBlameResult>("get_file_blame", { path, file });
|
|
}
|
|
|
|
export function compareCommits(
|
|
path: string,
|
|
from: string,
|
|
to: string,
|
|
): Promise<GitCommitComparison> {
|
|
return invoke<GitCommitComparison>("compare_commits", { path, from, to });
|
|
}
|
|
|
|
export function diffFileAgainstWorkingTree(
|
|
path: string,
|
|
commit: string,
|
|
file: string,
|
|
): Promise<GitCommitComparison> {
|
|
return invoke<GitCommitComparison>("diff_file_against_working_tree", { path, commit, file });
|
|
}
|
|
|
|
export function compareFileToHead(
|
|
path: string,
|
|
commit: string,
|
|
file: string,
|
|
): Promise<GitCommitComparison> {
|
|
return invoke<GitCommitComparison>("compare_file_to_head", { path, commit, file });
|
|
}
|
|
|
|
export function compareFileToParent(
|
|
path: string,
|
|
commit: string,
|
|
file: string,
|
|
oldFile: string | null = null,
|
|
): Promise<GitCommitComparison> {
|
|
return invoke<GitCommitComparison>("compare_file_to_parent", { path, commit, file, oldFile });
|
|
}
|
|
|
|
export function searchCodeIntroductions(
|
|
path: string,
|
|
query: string,
|
|
caseSensitive = false,
|
|
limit = 250,
|
|
searchId?: string,
|
|
): Promise<GitSearchHit[]> {
|
|
return invoke<GitSearchHit[]>("search_code_introductions", {
|
|
path,
|
|
query,
|
|
caseSensitive,
|
|
limit,
|
|
searchId: searchId ?? null,
|
|
});
|
|
}
|
|
|
|
export function cancelCodeSearch(searchId: string): Promise<void> {
|
|
return invoke<void>("cancel_code_search", { searchId });
|
|
}
|
|
|
|
export function readConflict(path: string, file: string): Promise<ConflictFile> {
|
|
return invoke<ConflictFile>("read_conflict", { path, file });
|
|
}
|
|
|
|
export function resolveConflict(
|
|
path: string,
|
|
file: string,
|
|
content: string,
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("resolve_conflict", { path, file, content });
|
|
}
|
|
|
|
export function resolveConflictSide(
|
|
path: string,
|
|
file: string,
|
|
side: "ours" | "theirs",
|
|
): Promise<GitStatus> {
|
|
return invoke<GitStatus>("resolve_conflict_side", { path, file, side });
|
|
}
|