forked from op7418/CodePilot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGitBranchSelector.tsx
More file actions
107 lines (96 loc) · 3.76 KB
/
GitBranchSelector.tsx
File metadata and controls
107 lines (96 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"use client";
import { useState, useEffect } from "react";
import { GitBranch, Check, Lock } from "@/components/ui/icon";
import { Button } from "@/components/ui/button";
import { useTranslation } from "@/hooks/useTranslation";
import type { GitBranch as GitBranchType } from "@/types";
interface GitBranchSelectorProps {
cwd: string;
currentBranch: string;
dirty: boolean;
onCheckout: (branch: string) => Promise<void>;
error?: string | null;
}
export function GitBranchSelector({ cwd, currentBranch, dirty, onCheckout, error }: GitBranchSelectorProps) {
const { t } = useTranslation();
const [branches, setBranches] = useState<GitBranchType[]>([]);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [checkingOut, setCheckingOut] = useState<string | null>(null);
useEffect(() => {
if (!isOpen || !cwd) return;
setLoading(true);
fetch(`/api/git/branches?cwd=${encodeURIComponent(cwd)}`)
.then(res => res.json())
.then(data => setBranches(data.branches || []))
.catch(() => {})
.finally(() => setLoading(false));
}, [isOpen, cwd]);
const [localError, setLocalError] = useState<string | null>(null);
const handleCheckout = async (branch: string) => {
if (dirty || branch === currentBranch) return;
setCheckingOut(branch);
setLocalError(null);
try {
await onCheckout(branch);
setIsOpen(false);
} catch (err) {
setLocalError(err instanceof Error ? err.message : 'Checkout failed');
} finally {
setCheckingOut(null);
}
};
const localBranches = branches.filter(b => !b.isRemote);
return (
<div className="space-y-2">
<Button
variant="ghost"
size="sm"
className="w-full justify-start text-xs"
onClick={() => setIsOpen(!isOpen)}
>
<GitBranch size={14} className="mr-1.5" />
{t('git.branchSelector')}
</Button>
{(error || localError) && (
<p className="px-3 text-[11px] text-destructive">{error || localError}</p>
)}
{isOpen && (
<div className="border rounded-md bg-background max-h-[200px] overflow-y-auto">
{loading ? (
<div className="p-2 text-[11px] text-muted-foreground">{t('git.loading')}</div>
) : (
localBranches.map(branch => {
const isCurrent = branch.name === currentBranch;
const isOccupied = !!branch.worktreePath && !isCurrent;
const disabled = dirty || isOccupied || isCurrent;
return (
<button
key={branch.name}
className="flex items-center gap-2 w-full px-3 py-1.5 text-[12px] text-left hover:bg-muted/50 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={disabled || checkingOut !== null}
onClick={() => handleCheckout(branch.name)}
>
{isCurrent && <Check size={12} className="text-green-500 shrink-0" />}
{isOccupied && <Lock size={12} className="text-muted-foreground shrink-0" />}
{!isCurrent && !isOccupied && <span className="w-3 shrink-0" />}
<span className="truncate">{branch.name}</span>
{isOccupied && (
<span className="ml-auto text-[10px] text-muted-foreground shrink-0">
{t('git.worktreeOccupied')}
</span>
)}
{dirty && !isCurrent && !isOccupied && (
<span className="ml-auto text-[10px] text-amber-500 shrink-0">
{t('git.dirtyWorkTree')}
</span>
)}
</button>
);
})
)}
</div>
)}
</div>
);
}