forked from op7418/CodePilot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGitWorktreeSection.tsx
More file actions
129 lines (118 loc) · 4.16 KB
/
GitWorktreeSection.tsx
File metadata and controls
129 lines (118 loc) · 4.16 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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"use client";
import { useState, useEffect } from "react";
import { Folder, DotOutline, ArrowRight, Plus } from "@/components/ui/icon";
import { Button } from "@/components/ui/button";
import { useTranslation } from "@/hooks/useTranslation";
import { useRouter } from "next/navigation";
import type { GitWorktree } from "@/types";
interface GitWorktreeSectionProps {
cwd: string;
onDeriveWorktree: () => void;
}
export function GitWorktreeSection({ cwd, onDeriveWorktree }: GitWorktreeSectionProps) {
const { t } = useTranslation();
const router = useRouter();
const [worktrees, setWorktrees] = useState<GitWorktree[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!cwd) return;
let cancelled = false;
(async () => {
setLoading(true);
try {
const res = await fetch(`/api/git/worktrees?cwd=${encodeURIComponent(cwd)}`);
const data = await res.json();
if (!cancelled) setWorktrees(data.worktrees || []);
} catch {
// ignore
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => { cancelled = true; };
}, [cwd]);
const handleSwitchTo = async (worktreePath: string) => {
// Find or create a session for this worktree's working directory
try {
const res = await fetch(`/api/chat/sessions/by-cwd?cwd=${encodeURIComponent(worktreePath)}`);
if (res.ok) {
const data = await res.json();
if (data.sessionId) {
router.push(`/chat/${data.sessionId}`);
return;
}
}
// No existing session — create one
const createRes = await fetch('/api/chat/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ working_directory: worktreePath }),
});
if (createRes.ok) {
const data = await createRes.json();
router.push(`/chat/${data.session.id}`);
}
} catch {
// fallback: just navigate to chat
}
};
if (loading) {
return <div className="px-3 py-2 text-[11px] text-muted-foreground">{t('git.loading')}</div>;
}
const isCurrent = (wt: GitWorktree) => {
// Normalize paths for comparison
const normalize = (p: string) => p.replace(/\/+$/, '');
return normalize(wt.path) === normalize(cwd);
};
return (
<div className="space-y-1">
{worktrees.map(wt => {
const current = isCurrent(wt);
return (
<div
key={wt.path}
className={`flex items-center gap-2 px-3 py-1.5 ${current ? 'bg-muted/30' : 'hover:bg-muted/20'}`}
>
<Folder size={14} className={current ? 'text-foreground shrink-0' : 'text-muted-foreground shrink-0'} />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1">
<span className={`text-[12px] truncate ${current ? 'font-medium' : ''}`}>
{wt.branch || wt.head.substring(0, 7)}
</span>
{current && (
<span className="text-[9px] text-muted-foreground bg-muted rounded px-1">{t('git.current')}</span>
)}
{wt.dirty && (
<DotOutline size={10} weight="fill" className="text-amber-500 shrink-0" />
)}
</div>
<p className="text-[10px] text-muted-foreground truncate">{wt.path}</p>
</div>
{!current && !wt.bare && (
<Button
variant="ghost"
size="icon-sm"
className="shrink-0 text-muted-foreground hover:text-foreground"
onClick={() => handleSwitchTo(wt.path)}
>
<ArrowRight size={12} />
<span className="sr-only">{t('git.switchWorktree')}</span>
</Button>
)}
</div>
);
})}
<div className="px-3 pt-1">
<Button
variant="outline"
size="sm"
className="w-full text-xs"
onClick={onDeriveWorktree}
>
<Plus size={14} className="mr-1.5" />
{t('git.deriveWorktree')}
</Button>
</div>
</div>
);
}