* chore: remove dead/unreferenced files across ui, vscode Remove 59 unused source files (components, hooks, lib utils, stores, barrels, and orphaned vscode github modules) that are not imported by any entry-reachable code. Also drop a stale test mock for the removed execCommands module. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * refactor: remove unused exported symbols (types, functions, consts, hooks) Remove exported symbols whose identifier is referenced nowhere in the repository (verified via repo-wide search), across ui types/contracts, lib utilities, sync layer, stores, and components. Also drop the few imports/private helpers orphaned by these removals. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * refactor: remove more unused exports (desktop, shortcuts, worktree, vscode) Continue removing repo-wide unreferenced exported functions, consts and types across lib/desktop, shortcuts, worktreeSessionCreator, sync, and vscode gitService, with cascading orphaned helpers/imports cleaned up. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * chore: add dead-code cleanup tooling * refactor: checkpoint dead-code cleanup * refactor: remove dead-code suppressions --------- Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
208 lines
7.4 KiB
TypeScript
208 lines
7.4 KiB
TypeScript
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<boolean | null>(() => {
|
|
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<BranchSelectorProps> = ({
|
|
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 (
|
|
<div>
|
|
<Select
|
|
value={value}
|
|
onValueChange={onChange}
|
|
disabled={isDisabled}
|
|
>
|
|
<SelectTrigger
|
|
id={id}
|
|
size="lg"
|
|
className={className ?? 'w-fit typography-meta text-foreground'}
|
|
>
|
|
<SelectValue placeholder={isLoading ? t('multiRun.branchSelector.status.loadingBranches') : t('multiRun.branchSelector.placeholder.selectSourceBranch')} />
|
|
</SelectTrigger>
|
|
<SelectContent className="max-h-[280px] max-w-[320px]">
|
|
{isLoading ? (
|
|
<div className="px-2 py-4 text-center typography-meta text-muted-foreground">
|
|
{t('multiRun.branchSelector.status.loadingBranches')}
|
|
</div>
|
|
) : localBranches.length === 0 && remoteBranches.length === 0 ? (
|
|
<div className="px-2 py-4 text-center typography-meta text-muted-foreground">
|
|
{t('multiRun.branchSelector.status.noBranchesFound')}
|
|
</div>
|
|
) : (
|
|
<>
|
|
{localBranches.length > 0 && (
|
|
<SelectGroup>
|
|
<SelectLabel className="font-semibold text-foreground">{t('multiRun.branchSelector.groups.localBranches')}</SelectLabel>
|
|
{localBranches.map((branch) => (
|
|
<SelectItem key={branch} value={branch} className="whitespace-normal break-all">
|
|
{branch}
|
|
</SelectItem>
|
|
))}
|
|
</SelectGroup>
|
|
)}
|
|
{localBranches.length > 0 && remoteBranches.length > 0 && (
|
|
<SelectSeparator />
|
|
)}
|
|
{remoteBranches.length > 0 && (
|
|
<SelectGroup>
|
|
<SelectLabel className="font-semibold text-foreground">{t('multiRun.branchSelector.groups.remoteBranches')}</SelectLabel>
|
|
{remoteBranches.map((branch) => (
|
|
<SelectItem key={`remotes/${branch}`} value={`remotes/${branch}`} className="whitespace-normal break-all">
|
|
{branch}
|
|
</SelectItem>
|
|
))}
|
|
</SelectGroup>
|
|
)}
|
|
</>
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
|
|
{isGitRepository === false && (
|
|
<p className="typography-micro text-muted-foreground/70 mt-2">{t('multiRun.branchSelector.status.notInGitRepository')}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|