import { useCallback, useEffect, useState } from "react"; import { checkoutBranch, createBranch, fetchRepository, getRepositoryStatus, pullRepository, pushRepository, type GitStatus, } from "../api/git_repositories"; import { Icon } from "./icon"; import { MergeDialog } from "./merge-dialog"; interface GitToolbarProps { projectId: string; repoId: string; currentBranch: string; branches: string[]; hasRemote: boolean; isMirror: boolean; onBranchChange: (branch: string) => void; onRefresh: () => void; } export const GitToolbar = ({ projectId, repoId, currentBranch, branches, hasRemote, isMirror, onBranchChange, onRefresh, }: GitToolbarProps) => { const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showNewBranch, setShowNewBranch] = useState(false); const [newBranchName, setNewBranchName] = useState(""); const [newBranchBase, setNewBranchBase] = useState(""); const [showMergeDialog, setShowMergeDialog] = useState(false); const loadStatus = useCallback(async () => { try { const data = await getRepositoryStatus(projectId, repoId); setStatus(data); setError(null); } catch { setError("Failed to load status"); } }, [projectId, repoId]); useEffect(() => { void loadStatus(); // Poll status every 5 seconds const interval = setInterval(() => void loadStatus(), 5000); return () => clearInterval(interval); }, [loadStatus]); const handleFetch = async () => { if (!hasRemote) return; setLoading(true); try { await fetchRepository(projectId, repoId); await loadStatus(); } catch { setError("Fetch failed"); } finally { setLoading(false); } }; const handlePull = async () => { if (!hasRemote) return; setLoading(true); try { await pullRepository(projectId, repoId, currentBranch || undefined); await loadStatus(); onRefresh(); } catch { setError("Pull failed"); } finally { setLoading(false); } }; const handlePush = async () => { setLoading(true); try { await pushRepository(projectId, repoId, currentBranch); await loadStatus(); } catch { setError("Push failed"); } finally { setLoading(false); } }; const handleCheckout = async (branch: string) => { setLoading(true); try { await checkoutBranch(projectId, repoId, branch); onBranchChange(branch); onRefresh(); } catch { setError("Checkout failed"); } finally { setLoading(false); } }; const handleCreateBranch = async () => { if (!newBranchName.trim()) return; setLoading(true); try { await createBranch(projectId, repoId, newBranchName, newBranchBase || currentBranch || "HEAD"); setShowNewBranch(false); setNewBranchName(""); setNewBranchBase(""); onRefresh(); } catch { setError("Failed to create branch"); } finally { setLoading(false); } }; const hasChanges = status && ( status.modified.length > 0 || status.added.length > 0 || status.deleted.length > 0 || status.untracked.length > 0 ); const canSync = hasRemote; return (
{error &&
{error}
} {isMirror && (
This repository is a bare mirror. Editing, committing, pulling, and merging are not available. Delete and recreate it to enable full workspace features.
)}
{showNewBranch && (
setNewBranchName(e.target.value)} className="toolbar-input" />
)} {hasChanges && status && (
{status.modified.length > 0 && {status.modified.length} modified} {status.added.length > 0 && {status.added.length} added} {status.deleted.length > 0 && {status.deleted.length} deleted} {status.untracked.length > 0 && {status.untracked.length} untracked}
)} setShowMergeDialog(false)} onMerge={() => { void loadStatus(); onRefresh(); }} />
); };