import React from 'react'; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { useGitStore, useGitBranches, useGitLoadingBranches, useGitLoadingStatus, useIsGitRepo } from '@/stores/useGitStore'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { getRootBranch } from '@/lib/worktrees/worktreeStatus'; import { useI18n } from '@/lib/i18n'; /** localStorage key matching NewWorktreeDialog */ const LAST_SOURCE_BRANCH_KEY = 'oc:lastWorktreeSourceBranch'; export interface BranchSelectorProps { /** Current directory to check for git repository */ directory: string | null; /** Currently selected branch */ value: string; /** Called when branch selection changes */ onChange: (branch: string) => void; /** Optional className for the trigger */ className?: string; /** Whether the selector is disabled */ disabled?: boolean; /** ID for accessibility */ id?: string; } export interface BranchSelectorState { localBranches: string[]; remoteBranches: string[]; isLoading: boolean; isGitRepository: boolean | null; } /** * Hook to load available git branches for a directory. * Uses the shared useGitStore (same as NewWorktreeDialog). */ // eslint-disable-next-line react-refresh/only-export-components -- Hook is tightly coupled with BranchSelector export function useBranchOptions(directory: string | null): BranchSelectorState { const { git } = useRuntimeAPIs(); const branches = useGitBranches(directory); const isGitRepo = useIsGitRepo(directory); const isLoadingStatus = useGitLoadingStatus(directory); const isLoadingBranches = useGitLoadingBranches(directory); const fetchBranches = useGitStore((state) => state.fetchBranches); const fetchStatus = useGitStore((state) => state.fetchStatus); React.useEffect(() => { if (!directory || !git || isGitRepo !== null || isLoadingStatus) return; void fetchStatus(directory, git, { silent: true }); }, [directory, git, fetchStatus, isGitRepo, isLoadingStatus]); // Fetch branches if not cached React.useEffect(() => { if (!directory || !git) return; if (isGitRepo !== true) return; if (branches?.all) return; // Already cached void fetchBranches(directory, git); }, [directory, git, isGitRepo, branches?.all, fetchBranches]); const isLoading = isLoadingStatus || (isGitRepo === true && isLoadingBranches); // Compute local and remote branch lists (same as NewWorktreeDialog) const localBranches = React.useMemo(() => { if (!branches?.all) return []; return branches.all .filter((branchName: string) => !branchName.startsWith('remotes/')) .sort(); }, [branches]); const remoteBranches = React.useMemo(() => { if (!branches?.all) return []; return branches.all .filter((branchName: string) => branchName.startsWith('remotes/')) .map((branchName: string) => branchName.replace(/^remotes\//, '')) .sort(); }, [branches]); // isGitRepository: true if we got branches, false if fetch returned empty, null if not yet loaded const isGitRepository = React.useMemo(() => { if (!directory) return null; if (isGitRepo !== null) return isGitRepo; if (isLoading) return null; return branches?.all ? true : null; }, [directory, isLoading, branches, isGitRepo]); return { localBranches, remoteBranches, isLoading, isGitRepository }; } /** * Branch selector dropdown for selecting a source branch for worktree creation. * Matches the NewWorktreeDialog source branch selector exactly. */ export const BranchSelector: React.FC = ({ directory, value, onChange, className, disabled, id, }) => { const { t } = useI18n(); const { localBranches, remoteBranches, isLoading, isGitRepository } = useBranchOptions(directory); const allBranches = React.useMemo( () => [...localBranches, ...remoteBranches.map(b => `remotes/${b}`)], [localBranches, remoteBranches], ); // Resolve default source branch (same priority as NewWorktreeDialog) React.useEffect(() => { if (disabled || isLoading || allBranches.length === 0) return; // If current value is valid, keep it if (value && allBranches.includes(value)) return; const resolve = async () => { try { const rootBranch = directory ? await getRootBranch(directory).catch(() => null) : null; const saved = localStorage.getItem(LAST_SOURCE_BRANCH_KEY); if (saved && allBranches.includes(saved)) { onChange(saved); } else if (rootBranch && allBranches.includes(rootBranch)) { onChange(rootBranch); } else if (allBranches.includes('main')) { onChange('main'); } else if (allBranches.includes('master')) { onChange('master'); } else if (allBranches[0]) { onChange(allBranches[0]); } } catch { // ignore } }; void resolve(); }, [allBranches, directory, disabled, isLoading, onChange, value]); const isDisabled = disabled || !isGitRepository || isLoading; return (
{isGitRepository === false && (

{t('multiRun.branchSelector.status.notInGitRepository')}

)}
); };