import type { GitListBranchesResult, GitStackedAction, NativeApi } from "@t3tools/contracts"; import { mutationOptions, queryOptions, type QueryClient } from "@tanstack/react-query"; export const gitQueryKeys = { all: ["git"] as const, status: (cwd: string | null) => ["git", "status", cwd] as const, branches: (cwd: string | null) => ["git", "branches", cwd] as const, }; export function invalidateGitQueries(queryClient: QueryClient) { return queryClient.invalidateQueries({ queryKey: gitQueryKeys.all }); } export function projectGitBranchesToCurrent( existing: GitListBranchesResult | undefined, currentBranch: string, ): GitListBranchesResult | undefined { if (!existing) return existing; let nextCurrent: GitListBranchesResult["branches"][number] | null = null; const remaining: GitListBranchesResult["branches"] = []; for (const branch of existing.branches) { if (branch.name === currentBranch) { nextCurrent = branch.current ? branch : { ...branch, current: true }; continue; } remaining.push(branch.current ? { ...branch, current: false } : branch); } if (!nextCurrent) { nextCurrent = { name: currentBranch, current: true, isDefault: false, worktreePath: null, }; } return { ...existing, branches: [nextCurrent, ...remaining], }; } export function setGitBranchesCurrentBranch( queryClient: QueryClient, cwd: string | null, currentBranch: string, ) { if (!cwd) return; queryClient.setQueryData(gitQueryKeys.branches(cwd), (existing) => projectGitBranchesToCurrent(existing, currentBranch), ); } export function gitStatusQueryOptions(api: NativeApi | undefined, cwd: string | null) { return queryOptions({ queryKey: gitQueryKeys.status(cwd), queryFn: async () => { if (!api || !cwd) { throw new Error("Git status is unavailable."); } return api.git.status({ cwd }); }, enabled: !!api && !!cwd, }); } export function gitBranchesQueryOptions(api: NativeApi | undefined, cwd: string | null) { return queryOptions({ queryKey: gitQueryKeys.branches(cwd), queryFn: async () => { if (!api || !cwd) { throw new Error("Git branches are unavailable."); } return api.git.listBranches({ cwd }); }, enabled: !!api && !!cwd, }); } export function gitInitMutationOptions(input: { api: NativeApi | undefined; cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationFn: async () => { if (!input.api || !input.cwd) throw new Error("Git init is unavailable."); return input.api.git.init({ cwd: input.cwd }); }, onSuccess: async () => { await invalidateGitQueries(input.queryClient); }, }); } export function gitCheckoutMutationOptions(input: { api: NativeApi | undefined; cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationFn: async (branch: string) => { if (!input.api || !input.cwd) throw new Error("Git checkout is unavailable."); return input.api.git.checkout({ cwd: input.cwd, branch }); }, onSuccess: async (_result, branch) => { setGitBranchesCurrentBranch(input.queryClient, input.cwd, branch); await invalidateGitQueries(input.queryClient); }, }); } export function gitCreateBranchAndCheckoutMutationOptions(input: { api: NativeApi | undefined; cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationFn: async (branch: string) => { if (!input.api || !input.cwd) throw new Error("Git branch creation is unavailable."); await input.api.git.createBranch({ cwd: input.cwd, branch }); return input.api.git.checkout({ cwd: input.cwd, branch }); }, onSuccess: async (_result, branch) => { setGitBranchesCurrentBranch(input.queryClient, input.cwd, branch); await invalidateGitQueries(input.queryClient); }, }); } export function gitRunStackedActionMutationOptions(input: { api: NativeApi | undefined; cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationFn: async ({ action, commitMessage, }: { action: GitStackedAction; commitMessage?: string; }) => { if (!input.api || !input.cwd) { throw new Error("Git action is unavailable."); } return input.api.git.runStackedAction({ cwd: input.cwd, action, ...(commitMessage ? { commitMessage } : {}), }); }, onSettled: async () => { await invalidateGitQueries(input.queryClient); }, }); } export function gitPullMutationOptions(input: { api: NativeApi | undefined; cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationFn: async () => { if (!input.api || !input.cwd) throw new Error("Git pull is unavailable."); return input.api.git.pull({ cwd: input.cwd }); }, onSettled: async () => { await invalidateGitQueries(input.queryClient); }, }); } export function gitCreateWorktreeMutationOptions(input: { api: NativeApi | undefined; queryClient: QueryClient; }) { return mutationOptions({ mutationFn: async ({ cwd, branch, newBranch, }: { cwd: string; branch: string; newBranch: string; }) => { if (!input.api) { throw new Error("Git worktree creation is unavailable."); } return input.api.git.createWorktree({ cwd, branch, newBranch }); }, onSettled: async () => { await invalidateGitQueries(input.queryClient); }, }); } export function gitRemoveWorktreeMutationOptions(input: { api: NativeApi | undefined; queryClient: QueryClient; }) { return mutationOptions({ mutationFn: async ({ cwd, path }: { cwd: string; path: string }) => { if (!input.api) { throw new Error("Git worktree removal is unavailable."); } return input.api.git.removeWorktree({ cwd, path }); }, onSettled: async () => { await invalidateGitQueries(input.queryClient); }, }); }