Skip to content

Commit c39764e

Browse files
authored
Merge pull request #38 from beNative/codex/add-checkout-branch-functionality-to-repoformmodal
Add branch checkout workflow to repo modal
2 parents 6ebace0 + 32c908e commit c39764e

1 file changed

Lines changed: 87 additions & 18 deletions

File tree

components/modals/RepoFormModal.tsx

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,13 +1167,25 @@ const RepoEditView: React.FC<RepoEditViewProps> = ({ onSave, onCancel, repositor
11671167
const [newBranchName, setNewBranchName] = useState('');
11681168
const [branchToMerge, setBranchToMerge] = useState('');
11691169
const [selectedBranch, setSelectedBranch] = useState<{ name: string; scope: 'local' | 'remote' } | null>(null);
1170+
const [isCheckoutLoading, setIsCheckoutLoading] = useState(false);
11701171
const [branchFilter, setBranchFilter] = useState('');
11711172
const [debouncedBranchFilter, setDebouncedBranchFilter] = useState('');
11721173
const branchItemRefs = useRef<{ local: Map<string, HTMLDivElement>; remote: Map<string, HTMLDivElement> }>({
11731174
local: new Map<string, HTMLDivElement>(),
11741175
remote: new Map<string, HTMLDivElement>(),
11751176
});
11761177

1178+
const normalizedSelectedBranchName = useMemo(() => {
1179+
if (!selectedBranch) {
1180+
return '';
1181+
}
1182+
if (selectedBranch.scope === 'remote') {
1183+
const segments = selectedBranch.name.split('/').slice(1);
1184+
return segments.join('/') || selectedBranch.name;
1185+
}
1186+
return selectedBranch.name;
1187+
}, [selectedBranch]);
1188+
11771189
// State for Releases Tab
11781190
const [releases, setReleases] = useState<ReleaseInfo[] | null>(null);
11791191
const [releasesLoading, setReleasesLoading] = useState(false);
@@ -1611,6 +1623,44 @@ const RepoEditView: React.FC<RepoEditViewProps> = ({ onSave, onCancel, repositor
16111623
element?.focus();
16121624
}, []);
16131625

1626+
const handleCheckoutBranch = useCallback(async () => {
1627+
if (!repository || !selectedBranch) {
1628+
return;
1629+
}
1630+
1631+
const currentBranch = branchInfo?.current;
1632+
const focusTarget = normalizedSelectedBranchName || selectedBranch.name;
1633+
const checkoutLabel = selectedBranch.name;
1634+
1635+
if (currentBranch && normalizedSelectedBranchName && normalizedSelectedBranchName === currentBranch) {
1636+
setToast({ message: `Already on '${currentBranch}'.`, type: 'info' });
1637+
return;
1638+
}
1639+
1640+
setIsCheckoutLoading(true);
1641+
const checkoutTarget = selectedBranch.name;
1642+
1643+
try {
1644+
const result = await window.electronAPI?.checkoutBranch(repository.localPath, checkoutTarget);
1645+
if (result?.success) {
1646+
setToast({ message: `Checked out '${checkoutLabel}'.`, type: 'success' });
1647+
await fetchBranches();
1648+
await onRefreshState(repository.id);
1649+
setBranchToMerge('');
1650+
setSelectedBranch(null);
1651+
if (focusTarget) {
1652+
requestAnimationFrame(() => focusBranchItem('local', focusTarget));
1653+
}
1654+
} else {
1655+
setToast({ message: `Checkout failed: ${result?.error || 'Electron API not available.'}`, type: 'error' });
1656+
}
1657+
} catch (error: any) {
1658+
setToast({ message: `Checkout failed: ${error.message}`, type: 'error' });
1659+
} finally {
1660+
setIsCheckoutLoading(false);
1661+
}
1662+
}, [repository, selectedBranch, branchInfo?.current, normalizedSelectedBranchName, setToast, fetchBranches, onRefreshState, focusBranchItem]);
1663+
16141664
const handleBranchKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>, branchName: string, scope: 'local' | 'remote') => {
16151665
if (event.key === 'Enter' || event.key === ' ') {
16161666
event.preventDefault();
@@ -1853,7 +1903,12 @@ const RepoEditView: React.FC<RepoEditViewProps> = ({ onSave, onCancel, repositor
18531903
</div>
18541904
</div>
18551905
);
1856-
case 'branches':
1906+
case 'branches': {
1907+
const isCurrentSelection = Boolean(normalizedSelectedBranchName && branchInfo?.current && normalizedSelectedBranchName === branchInfo.current);
1908+
const checkoutDisabled = !selectedBranch || isCheckoutLoading || branchesLoading || isCurrentSelection;
1909+
const selectionDescription = selectedBranch
1910+
? `${selectedBranch.scope === 'remote' ? 'Remote' : 'Local'} branch: ${selectedBranch.name}`
1911+
: 'Select a branch to checkout.';
18571912
return (
18581913
<div className="flex-1 flex flex-col overflow-hidden">
18591914
<div className="flex-1 flex flex-col overflow-hidden p-4 gap-4">
@@ -1968,24 +2023,37 @@ const RepoEditView: React.FC<RepoEditViewProps> = ({ onSave, onCancel, repositor
19682023
</ul>
19692024
</div>
19702025
</div>
1971-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pt-4 mt-2 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
1972-
<div>
1973-
<h4 className="font-semibold mb-2">Create New Branch</h4>
1974-
<div className="flex gap-2">
1975-
<input type="text" value={newBranchName} onChange={e => setNewBranchName(e.target.value)} placeholder="new-branch-name" className={formInputStyle}/>
1976-
<button type="button" onClick={handleCreateBranch} className="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-medium hover:bg-green-700">Create</button>
1977-
</div>
2026+
<div className="pt-4 mt-2 border-t border-gray-200 dark:border-gray-700 flex flex-col gap-4 flex-shrink-0">
2027+
<div className="flex flex-wrap items-center justify-between gap-3">
2028+
<p className="text-sm text-gray-600 dark:text-gray-300">{selectionDescription}</p>
2029+
<button
2030+
type="button"
2031+
onClick={handleCheckoutBranch}
2032+
disabled={checkoutDisabled}
2033+
className={`px-4 py-2 rounded-md text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:bg-gray-400 disabled:cursor-not-allowed ${isCheckoutLoading ? 'cursor-wait' : ''}`}
2034+
>
2035+
{isCheckoutLoading ? 'Checking out...' : 'Checkout'}
2036+
</button>
19782037
</div>
1979-
<div>
1980-
<h4 className="font-semibold mb-2">Merge Branch into Current</h4>
1981-
<div className="flex gap-2">
1982-
<select value={branchToMerge || ''} onChange={e => setBranchToMerge(e.target.value)} className={formInputStyle}>
1983-
<option value="" disabled>Select a branch</option>
1984-
{(branchInfo?.local || []).filter(b => b !== branchInfo?.current).map(b => (
1985-
<option key={b} value={b}>{b}</option>
1986-
))}
1987-
</select>
1988-
<button type="button" onClick={handleMergeBranch} className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm font-medium hover:bg-indigo-700">Merge</button>
2038+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
2039+
<div>
2040+
<h4 className="font-semibold mb-2">Create New Branch</h4>
2041+
<div className="flex gap-2">
2042+
<input type="text" value={newBranchName} onChange={e => setNewBranchName(e.target.value)} placeholder="new-branch-name" className={formInputStyle}/>
2043+
<button type="button" onClick={handleCreateBranch} className="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-medium hover:bg-green-700">Create</button>
2044+
</div>
2045+
</div>
2046+
<div>
2047+
<h4 className="font-semibold mb-2">Merge Branch into Current</h4>
2048+
<div className="flex gap-2">
2049+
<select value={branchToMerge || ''} onChange={e => setBranchToMerge(e.target.value)} className={formInputStyle}>
2050+
<option value="" disabled>Select a branch</option>
2051+
{(branchInfo?.local || []).filter(b => b !== branchInfo?.current).map(b => (
2052+
<option key={b} value={b}>{b}</option>
2053+
))}
2054+
</select>
2055+
<button type="button" onClick={handleMergeBranch} className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm font-medium hover:bg-indigo-700">Merge</button>
2056+
</div>
19892057
</div>
19902058
</div>
19912059
</div>
@@ -1994,6 +2062,7 @@ const RepoEditView: React.FC<RepoEditViewProps> = ({ onSave, onCancel, repositor
19942062
</div>
19952063
</div>
19962064
);
2065+
}
19972066
case 'releases':
19982067
if (!isGitHubRepo) return <div className="p-4 text-center text-gray-500">Release management is only available for repositories hosted on GitHub.</div>;
19992068
if (editingRelease) {

0 commit comments

Comments
 (0)