Merge pull request #251 from AutoMaker-Org/fix/list-branch-issue-on-fresh-repo

fix: branch list issue and improve ui feedback
This commit is contained in:
Web Dev Cody
2025-12-23 15:43:27 -05:00
committed by GitHub
18 changed files with 383 additions and 64 deletions

View File

@@ -111,6 +111,19 @@ export async function isGitRepo(repoPath: string): Promise<boolean> {
}
}
/**
* Check if a git repository has at least one commit (i.e., HEAD exists)
* Returns false for freshly initialized repos with no commits
*/
export async function hasCommits(repoPath: string): Promise<boolean> {
try {
await execAsync('git rev-parse --verify HEAD', { cwd: repoPath });
return true;
} catch {
return false;
}
}
/**
* Check if an error is ENOENT (file/path not found or spawn failed)
* These are expected in test environments with mock paths

View File

@@ -4,6 +4,7 @@
import { Router } from 'express';
import { validatePathParams } from '../../middleware/validate-paths.js';
import { requireValidWorktree, requireValidProject, requireGitRepoOnly } from './middleware.js';
import { createInfoHandler } from './routes/info.js';
import { createStatusHandler } from './routes/status.js';
import { createListHandler } from './routes/list.js';
@@ -38,17 +39,42 @@ export function createWorktreeRoutes(): Router {
router.post('/list', createListHandler());
router.post('/diffs', validatePathParams('projectPath'), createDiffsHandler());
router.post('/file-diff', validatePathParams('projectPath', 'filePath'), createFileDiffHandler());
router.post('/merge', validatePathParams('projectPath'), createMergeHandler());
router.post(
'/merge',
validatePathParams('projectPath'),
requireValidProject,
createMergeHandler()
);
router.post('/create', validatePathParams('projectPath'), createCreateHandler());
router.post('/delete', validatePathParams('projectPath', 'worktreePath'), createDeleteHandler());
router.post('/create-pr', createCreatePRHandler());
router.post('/pr-info', createPRInfoHandler());
router.post('/commit', validatePathParams('worktreePath'), createCommitHandler());
router.post('/push', validatePathParams('worktreePath'), createPushHandler());
router.post('/pull', validatePathParams('worktreePath'), createPullHandler());
router.post('/checkout-branch', createCheckoutBranchHandler());
router.post('/list-branches', validatePathParams('worktreePath'), createListBranchesHandler());
router.post('/switch-branch', createSwitchBranchHandler());
router.post(
'/commit',
validatePathParams('worktreePath'),
requireGitRepoOnly,
createCommitHandler()
);
router.post(
'/push',
validatePathParams('worktreePath'),
requireValidWorktree,
createPushHandler()
);
router.post(
'/pull',
validatePathParams('worktreePath'),
requireValidWorktree,
createPullHandler()
);
router.post('/checkout-branch', requireValidWorktree, createCheckoutBranchHandler());
router.post(
'/list-branches',
validatePathParams('worktreePath'),
requireValidWorktree,
createListBranchesHandler()
);
router.post('/switch-branch', requireValidWorktree, createSwitchBranchHandler());
router.post('/open-in-editor', validatePathParams('worktreePath'), createOpenInEditorHandler());
router.get('/default-editor', createGetDefaultEditorHandler());
router.post('/init-git', validatePathParams('projectPath'), createInitGitHandler());

View File

@@ -0,0 +1,74 @@
/**
* Middleware for worktree route validation
*/
import type { Request, Response, NextFunction } from 'express';
import { isGitRepo, hasCommits } from './common.js';
interface ValidationOptions {
/** Check if the path is a git repository (default: true) */
requireGitRepo?: boolean;
/** Check if the repository has at least one commit (default: true) */
requireCommits?: boolean;
/** The name of the request body field containing the path (default: 'worktreePath') */
pathField?: 'worktreePath' | 'projectPath';
}
/**
* Middleware factory to validate that a path is a valid git repository with commits.
* This reduces code duplication across route handlers.
*
* @param options - Validation options
* @returns Express middleware function
*/
export function requireValidGitRepo(options: ValidationOptions = {}) {
const { requireGitRepo = true, requireCommits = true, pathField = 'worktreePath' } = options;
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
const repoPath = req.body[pathField] as string | undefined;
if (!repoPath) {
// Let the route handler deal with missing path validation
next();
return;
}
if (requireGitRepo && !(await isGitRepo(repoPath))) {
res.status(400).json({
success: false,
error: 'Not a git repository',
code: 'NOT_GIT_REPO',
});
return;
}
if (requireCommits && !(await hasCommits(repoPath))) {
res.status(400).json({
success: false,
error: 'Repository has no commits yet',
code: 'NO_COMMITS',
});
return;
}
next();
};
}
/**
* Middleware to validate git repo for worktreePath field
*/
export const requireValidWorktree = requireValidGitRepo({ pathField: 'worktreePath' });
/**
* Middleware to validate git repo for projectPath field
*/
export const requireValidProject = requireValidGitRepo({ pathField: 'projectPath' });
/**
* Middleware to validate git repo without requiring commits (for commit route)
*/
export const requireGitRepoOnly = requireValidGitRepo({
pathField: 'worktreePath',
requireCommits: false,
});

View File

@@ -1,5 +1,8 @@
/**
* POST /checkout-branch endpoint - Create and checkout a new branch
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';

View File

@@ -1,5 +1,8 @@
/**
* POST /commit endpoint - Commit changes in a worktree
*
* Note: Git repository validation (isGitRepo) is handled by
* the requireGitRepoOnly middleware in index.ts
*/
import type { Request, Response } from 'express';

View File

@@ -1,5 +1,8 @@
/**
* POST /list-branches endpoint - List all local branches
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';

View File

@@ -1,5 +1,8 @@
/**
* POST /merge endpoint - Merge feature (merge worktree branch into main)
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidProject middleware in index.ts
*/
import type { Request, Response } from 'express';

View File

@@ -1,5 +1,8 @@
/**
* POST /pull endpoint - Pull latest changes for a worktree/branch
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';

View File

@@ -1,5 +1,8 @@
/**
* POST /push endpoint - Push a worktree branch to remote
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';

View File

@@ -4,6 +4,9 @@
* Simple branch switching.
* If there are uncommitted changes, the switch will fail and
* the user should commit first.
*
* Note: Git repository validation (isGitRepo, hasCommits) is handled by
* the requireValidWorktree middleware in index.ts
*/
import type { Request, Response } from 'express';

View File

@@ -0,0 +1,44 @@
import type { ReactElement, ReactNode } from 'react';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
interface TooltipWrapperProps {
/** The element to wrap with a tooltip */
children: ReactElement;
/** The content to display in the tooltip */
tooltipContent: ReactNode;
/** Whether to show the tooltip (if false, renders children without tooltip) */
showTooltip: boolean;
/** The side where the tooltip should appear */
side?: 'top' | 'right' | 'bottom' | 'left';
}
/**
* A reusable wrapper that conditionally adds a tooltip to its children.
* When showTooltip is false, it renders the children directly without any tooltip.
* This is useful for adding tooltips to disabled elements that need to show
* a reason for being disabled.
*/
export function TooltipWrapper({
children,
tooltipContent,
showTooltip,
side = 'left',
}: TooltipWrapperProps) {
if (!showTooltip) {
return children;
}
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
{/* The div wrapper is necessary for tooltips to work on disabled elements */}
<div>{children}</div>
</TooltipTrigger>
<TooltipContent side={side}>
<p>{tooltipContent}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

View File

@@ -20,9 +20,11 @@ import {
Globe,
MessageSquare,
GitMerge,
AlertCircle,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import type { WorktreeInfo, DevServerInfo, PRInfo } from '../types';
import type { WorktreeInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
import { TooltipWrapper } from './tooltip-wrapper';
interface WorktreeActionsDropdownProps {
worktree: WorktreeInfo;
@@ -35,6 +37,7 @@ interface WorktreeActionsDropdownProps {
isStartingDevServer: boolean;
isDevServerRunning: boolean;
devServerInfo?: DevServerInfo;
gitRepoStatus: GitRepoStatus;
onOpenChange: (open: boolean) => void;
onPull: (worktree: WorktreeInfo) => void;
onPush: (worktree: WorktreeInfo) => void;
@@ -60,6 +63,7 @@ export function WorktreeActionsDropdown({
isStartingDevServer,
isDevServerRunning,
devServerInfo,
gitRepoStatus,
onOpenChange,
onPull,
onPush,
@@ -76,6 +80,14 @@ export function WorktreeActionsDropdown({
// Check if there's a PR associated with this worktree from stored metadata
const hasPR = !!worktree.pr;
// Check git operations availability
const canPerformGitOps = gitRepoStatus.isGitRepo && gitRepoStatus.hasCommits;
const gitOpsDisabledReason = !gitRepoStatus.isGitRepo
? 'Not a git repository'
: !gitRepoStatus.hasCommits
? 'Repository has no commits yet'
: null;
return (
<DropdownMenu onOpenChange={onOpenChange}>
<DropdownMenuTrigger asChild>
@@ -92,6 +104,16 @@ export function WorktreeActionsDropdown({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
{/* Warning label when git operations are not available */}
{!canPerformGitOps && (
<>
<DropdownMenuLabel className="text-xs flex items-center gap-2 text-amber-600 dark:text-amber-400">
<AlertCircle className="w-3.5 h-3.5" />
{gitOpsDisabledReason}
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
)}
{isDevServerRunning ? (
<>
<DropdownMenuLabel className="text-xs flex items-center gap-2">
@@ -124,36 +146,58 @@ export function WorktreeActionsDropdown({
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem onClick={() => onPull(worktree)} disabled={isPulling} className="text-xs">
<Download className={cn('w-3.5 h-3.5 mr-2', isPulling && 'animate-pulse')} />
{isPulling ? 'Pulling...' : 'Pull'}
{behindCount > 0 && (
<span className="ml-auto text-[10px] bg-muted px-1.5 py-0.5 rounded">
{behindCount} behind
</span>
)}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onPush(worktree)}
disabled={isPushing || aheadCount === 0}
className="text-xs"
>
<Upload className={cn('w-3.5 h-3.5 mr-2', isPushing && 'animate-pulse')} />
{isPushing ? 'Pushing...' : 'Push'}
{aheadCount > 0 && (
<span className="ml-auto text-[10px] bg-primary/20 text-primary px-1.5 py-0.5 rounded">
{aheadCount} ahead
</span>
)}
</DropdownMenuItem>
{!worktree.isMain && (
<TooltipWrapper showTooltip={!!gitOpsDisabledReason} tooltipContent={gitOpsDisabledReason}>
<DropdownMenuItem
onClick={() => onResolveConflicts(worktree)}
className="text-xs text-purple-500 focus:text-purple-600"
onClick={() => canPerformGitOps && onPull(worktree)}
disabled={isPulling || !canPerformGitOps}
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
>
<GitMerge className="w-3.5 h-3.5 mr-2" />
Pull & Resolve Conflicts
<Download className={cn('w-3.5 h-3.5 mr-2', isPulling && 'animate-pulse')} />
{isPulling ? 'Pulling...' : 'Pull'}
{!canPerformGitOps && <AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />}
{canPerformGitOps && behindCount > 0 && (
<span className="ml-auto text-[10px] bg-muted px-1.5 py-0.5 rounded">
{behindCount} behind
</span>
)}
</DropdownMenuItem>
</TooltipWrapper>
<TooltipWrapper showTooltip={!!gitOpsDisabledReason} tooltipContent={gitOpsDisabledReason}>
<DropdownMenuItem
onClick={() => canPerformGitOps && onPush(worktree)}
disabled={isPushing || aheadCount === 0 || !canPerformGitOps}
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
>
<Upload className={cn('w-3.5 h-3.5 mr-2', isPushing && 'animate-pulse')} />
{isPushing ? 'Pushing...' : 'Push'}
{!canPerformGitOps && <AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />}
{canPerformGitOps && aheadCount > 0 && (
<span className="ml-auto text-[10px] bg-primary/20 text-primary px-1.5 py-0.5 rounded">
{aheadCount} ahead
</span>
)}
</DropdownMenuItem>
</TooltipWrapper>
{!worktree.isMain && (
<TooltipWrapper
showTooltip={!!gitOpsDisabledReason}
tooltipContent={gitOpsDisabledReason}
>
<DropdownMenuItem
onClick={() => canPerformGitOps && onResolveConflicts(worktree)}
disabled={!canPerformGitOps}
className={cn(
'text-xs text-purple-500 focus:text-purple-600',
!canPerformGitOps && 'opacity-50 cursor-not-allowed'
)}
>
<GitMerge className="w-3.5 h-3.5 mr-2" />
Pull & Resolve Conflicts
{!canPerformGitOps && (
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
)}
</DropdownMenuItem>
</TooltipWrapper>
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => onOpenInEditor(worktree)} className="text-xs">
@@ -162,17 +206,41 @@ export function WorktreeActionsDropdown({
</DropdownMenuItem>
<DropdownMenuSeparator />
{worktree.hasChanges && (
<DropdownMenuItem onClick={() => onCommit(worktree)} className="text-xs">
<GitCommit className="w-3.5 h-3.5 mr-2" />
Commit Changes
</DropdownMenuItem>
<TooltipWrapper
showTooltip={!gitRepoStatus.isGitRepo}
tooltipContent="Not a git repository"
>
<DropdownMenuItem
onClick={() => gitRepoStatus.isGitRepo && onCommit(worktree)}
disabled={!gitRepoStatus.isGitRepo}
className={cn('text-xs', !gitRepoStatus.isGitRepo && 'opacity-50 cursor-not-allowed')}
>
<GitCommit className="w-3.5 h-3.5 mr-2" />
Commit Changes
{!gitRepoStatus.isGitRepo && (
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
)}
</DropdownMenuItem>
</TooltipWrapper>
)}
{/* Show PR option for non-primary worktrees, or primary worktree with changes */}
{(!worktree.isMain || worktree.hasChanges) && !hasPR && (
<DropdownMenuItem onClick={() => onCreatePR(worktree)} className="text-xs">
<GitPullRequest className="w-3.5 h-3.5 mr-2" />
Create Pull Request
</DropdownMenuItem>
<TooltipWrapper
showTooltip={!!gitOpsDisabledReason}
tooltipContent={gitOpsDisabledReason}
>
<DropdownMenuItem
onClick={() => canPerformGitOps && onCreatePR(worktree)}
disabled={!canPerformGitOps}
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
>
<GitPullRequest className="w-3.5 h-3.5 mr-2" />
Create Pull Request
{!canPerformGitOps && (
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
)}
</DropdownMenuItem>
</TooltipWrapper>
)}
{/* Show PR info and Address Comments button if PR exists */}
{!worktree.isMain && hasPR && worktree.pr && (

View File

@@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button';
import { RefreshCw, Globe, Loader2, CircleDot, GitPullRequest } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo } from '../types';
import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
import { BranchSwitchDropdown } from './branch-switch-dropdown';
import { WorktreeActionsDropdown } from './worktree-actions-dropdown';
@@ -27,6 +27,7 @@ interface WorktreeTabProps {
isStartingDevServer: boolean;
aheadCount: number;
behindCount: number;
gitRepoStatus: GitRepoStatus;
onSelectWorktree: (worktree: WorktreeInfo) => void;
onBranchDropdownOpenChange: (open: boolean) => void;
onActionsDropdownOpenChange: (open: boolean) => void;
@@ -67,6 +68,7 @@ export function WorktreeTab({
isStartingDevServer,
aheadCount,
behindCount,
gitRepoStatus,
onSelectWorktree,
onBranchDropdownOpenChange,
onActionsDropdownOpenChange,
@@ -320,6 +322,7 @@ export function WorktreeTab({
isStartingDevServer={isStartingDevServer}
isDevServerRunning={isDevServerRunning}
devServerInfo={devServerInfo}
gitRepoStatus={gitRepoStatus}
onOpenChange={onActionsDropdownOpenChange}
onPull={onPull}
onPush={onPush}

View File

@@ -1,6 +1,6 @@
import { useState, useCallback } from 'react';
import { getElectronAPI } from '@/lib/electron';
import type { BranchInfo } from '../types';
import type { BranchInfo, GitRepoStatus } from '../types';
export function useBranches() {
const [branches, setBranches] = useState<BranchInfo[]>([]);
@@ -8,28 +8,58 @@ export function useBranches() {
const [behindCount, setBehindCount] = useState(0);
const [isLoadingBranches, setIsLoadingBranches] = useState(false);
const [branchFilter, setBranchFilter] = useState('');
const [gitRepoStatus, setGitRepoStatus] = useState<GitRepoStatus>({
isGitRepo: true,
hasCommits: true,
});
const fetchBranches = useCallback(async (worktreePath: string) => {
setIsLoadingBranches(true);
try {
const api = getElectronAPI();
if (!api?.worktree?.listBranches) {
console.warn('List branches API not available');
return;
}
const result = await api.worktree.listBranches(worktreePath);
if (result.success && result.result) {
setBranches(result.result.branches);
setAheadCount(result.result.aheadCount || 0);
setBehindCount(result.result.behindCount || 0);
}
} catch (error) {
console.error('Failed to fetch branches:', error);
} finally {
setIsLoadingBranches(false);
}
/** Helper to reset branch state to initial values */
const resetBranchState = useCallback(() => {
setBranches([]);
setAheadCount(0);
setBehindCount(0);
}, []);
const fetchBranches = useCallback(
async (worktreePath: string) => {
setIsLoadingBranches(true);
try {
const api = getElectronAPI();
if (!api?.worktree?.listBranches) {
console.warn('List branches API not available');
return;
}
const result = await api.worktree.listBranches(worktreePath);
if (result.success && result.result) {
setBranches(result.result.branches);
setAheadCount(result.result.aheadCount || 0);
setBehindCount(result.result.behindCount || 0);
setGitRepoStatus({ isGitRepo: true, hasCommits: true });
} else if (result.code === 'NOT_GIT_REPO') {
// Not a git repository - clear branches silently without logging an error
resetBranchState();
setGitRepoStatus({ isGitRepo: false, hasCommits: false });
} else if (result.code === 'NO_COMMITS') {
// Git repo but no commits yet - clear branches silently without logging an error
resetBranchState();
setGitRepoStatus({ isGitRepo: true, hasCommits: false });
} else if (!result.success) {
// Other errors - log them
console.warn('Failed to fetch branches:', result.error);
resetBranchState();
}
} catch (error) {
console.error('Failed to fetch branches:', error);
resetBranchState();
// Reset git status to unknown state on network/API errors
setGitRepoStatus({ isGitRepo: true, hasCommits: true });
} finally {
setIsLoadingBranches(false);
}
},
[resetBranchState]
);
const resetBranchFilter = useCallback(() => {
setBranchFilter('');
}, []);
@@ -48,5 +78,6 @@ export function useBranches() {
setBranchFilter,
resetBranchFilter,
fetchBranches,
gitRepoStatus,
};
}

View File

@@ -3,6 +3,29 @@ import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner';
import type { WorktreeInfo } from '../types';
// Error codes that need special user-friendly handling
const GIT_STATUS_ERROR_CODES = ['NOT_GIT_REPO', 'NO_COMMITS'] as const;
type GitStatusErrorCode = (typeof GIT_STATUS_ERROR_CODES)[number];
// User-friendly messages for git status errors
const GIT_STATUS_ERROR_MESSAGES: Record<GitStatusErrorCode, string> = {
NOT_GIT_REPO: 'This directory is not a git repository',
NO_COMMITS: 'Repository has no commits yet. Create an initial commit first.',
};
/**
* Helper to handle git status errors with user-friendly messages.
* @returns true if the error was a git status error and was handled, false otherwise.
*/
function handleGitStatusError(result: { code?: string; error?: string }): boolean {
const errorCode = result.code as GitStatusErrorCode | undefined;
if (errorCode && GIT_STATUS_ERROR_CODES.includes(errorCode)) {
toast.info(GIT_STATUS_ERROR_MESSAGES[errorCode] || result.error);
return true;
}
return false;
}
interface UseWorktreeActionsOptions {
fetchWorktrees: () => Promise<Array<{ path: string; branch: string }> | undefined>;
fetchBranches: (worktreePath: string) => Promise<void>;
@@ -29,6 +52,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
toast.success(result.result.message);
fetchWorktrees();
} else {
if (handleGitStatusError(result)) return;
toast.error(result.error || 'Failed to switch branch');
}
} catch (error) {
@@ -56,6 +80,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
toast.success(result.result.message);
fetchWorktrees();
} else {
if (handleGitStatusError(result)) return;
toast.error(result.error || 'Failed to pull latest changes');
}
} catch (error) {
@@ -84,6 +109,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
fetchBranches(worktree.path);
fetchWorktrees();
} else {
if (handleGitStatusError(result)) return;
toast.error(result.error || 'Failed to push changes');
}
} catch (error) {

View File

@@ -23,6 +23,11 @@ export interface BranchInfo {
isRemote: boolean;
}
export interface GitRepoStatus {
isGitRepo: boolean;
hasCommits: boolean;
}
export interface DevServerInfo {
worktreePath: string;
port: number;

View File

@@ -61,6 +61,7 @@ export function WorktreePanel({
setBranchFilter,
resetBranchFilter,
fetchBranches,
gitRepoStatus,
} = useBranches();
const {
@@ -210,6 +211,7 @@ export function WorktreePanel({
isStartingDevServer={isStartingDevServer}
aheadCount={aheadCount}
behindCount={behindCount}
gitRepoStatus={gitRepoStatus}
onSelectWorktree={handleSelectWorktree}
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(mainWorktree)}
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(mainWorktree)}
@@ -264,6 +266,7 @@ export function WorktreePanel({
isStartingDevServer={isStartingDevServer}
aheadCount={aheadCount}
behindCount={behindCount}
gitRepoStatus={gitRepoStatus}
onSelectWorktree={handleSelectWorktree}
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(worktree)}
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(worktree)}

View File

@@ -733,6 +733,7 @@ export interface WorktreeAPI {
message: string;
};
error?: string;
code?: 'NOT_GIT_REPO' | 'NO_COMMITS';
}>;
// Create a pull request from a worktree
@@ -783,6 +784,7 @@ export interface WorktreeAPI {
message: string;
};
error?: string;
code?: 'NOT_GIT_REPO' | 'NO_COMMITS';
}>;
// Create and checkout a new branch
@@ -797,6 +799,7 @@ export interface WorktreeAPI {
message: string;
};
error?: string;
code?: 'NOT_GIT_REPO' | 'NO_COMMITS';
}>;
// List all local branches
@@ -813,6 +816,7 @@ export interface WorktreeAPI {
behindCount: number;
};
error?: string;
code?: 'NOT_GIT_REPO' | 'NO_COMMITS'; // Error codes for git status issues
}>;
// Switch to an existing branch
@@ -827,6 +831,7 @@ export interface WorktreeAPI {
message: string;
};
error?: string;
code?: 'NOT_GIT_REPO' | 'NO_COMMITS' | 'UNCOMMITTED_CHANGES';
}>;
// Open a worktree directory in the editor