mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
fix: adress pr reviews
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { validatePathParams } from '../../middleware/validate-paths.js';
|
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||||
|
import { requireValidWorktree, requireValidProject, requireGitRepoOnly } from './middleware.js';
|
||||||
import { createInfoHandler } from './routes/info.js';
|
import { createInfoHandler } from './routes/info.js';
|
||||||
import { createStatusHandler } from './routes/status.js';
|
import { createStatusHandler } from './routes/status.js';
|
||||||
import { createListHandler } from './routes/list.js';
|
import { createListHandler } from './routes/list.js';
|
||||||
@@ -38,17 +39,42 @@ export function createWorktreeRoutes(): Router {
|
|||||||
router.post('/list', createListHandler());
|
router.post('/list', createListHandler());
|
||||||
router.post('/diffs', validatePathParams('projectPath'), createDiffsHandler());
|
router.post('/diffs', validatePathParams('projectPath'), createDiffsHandler());
|
||||||
router.post('/file-diff', validatePathParams('projectPath', 'filePath'), createFileDiffHandler());
|
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('/create', validatePathParams('projectPath'), createCreateHandler());
|
||||||
router.post('/delete', validatePathParams('projectPath', 'worktreePath'), createDeleteHandler());
|
router.post('/delete', validatePathParams('projectPath', 'worktreePath'), createDeleteHandler());
|
||||||
router.post('/create-pr', createCreatePRHandler());
|
router.post('/create-pr', createCreatePRHandler());
|
||||||
router.post('/pr-info', createPRInfoHandler());
|
router.post('/pr-info', createPRInfoHandler());
|
||||||
router.post('/commit', validatePathParams('worktreePath'), createCommitHandler());
|
router.post(
|
||||||
router.post('/push', validatePathParams('worktreePath'), createPushHandler());
|
'/commit',
|
||||||
router.post('/pull', validatePathParams('worktreePath'), createPullHandler());
|
validatePathParams('worktreePath'),
|
||||||
router.post('/checkout-branch', createCheckoutBranchHandler());
|
requireGitRepoOnly,
|
||||||
router.post('/list-branches', validatePathParams('worktreePath'), createListBranchesHandler());
|
createCommitHandler()
|
||||||
router.post('/switch-branch', createSwitchBranchHandler());
|
);
|
||||||
|
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.post('/open-in-editor', validatePathParams('worktreePath'), createOpenInEditorHandler());
|
||||||
router.get('/default-editor', createGetDefaultEditorHandler());
|
router.get('/default-editor', createGetDefaultEditorHandler());
|
||||||
router.post('/init-git', validatePathParams('projectPath'), createInitGitHandler());
|
router.post('/init-git', validatePathParams('projectPath'), createInitGitHandler());
|
||||||
|
|||||||
74
apps/server/src/routes/worktree/middleware.ts
Normal file
74
apps/server/src/routes/worktree/middleware.ts
Normal 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,
|
||||||
|
});
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* POST /checkout-branch endpoint - Create and checkout a new branch
|
* 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';
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -43,26 +46,6 @@ export function createCheckoutBranchHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if path is a git repository
|
|
||||||
if (!(await isGitRepo(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Not a git repository',
|
|
||||||
code: 'NOT_GIT_REPO',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if repository has at least one commit
|
|
||||||
if (!(await hasCommits(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Repository has no commits yet',
|
|
||||||
code: 'NO_COMMITS',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current branch for reference
|
// Get current branch for reference
|
||||||
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
||||||
cwd: worktreePath,
|
cwd: worktreePath,
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* POST /commit endpoint - Commit changes in a worktree
|
* 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';
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { getErrorMessage, logError, isGitRepo } from '../common.js';
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -25,16 +28,6 @@ export function createCommitHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if path is a git repository
|
|
||||||
if (!(await isGitRepo(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Not a git repository',
|
|
||||||
code: 'NOT_GIT_REPO',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for uncommitted changes
|
// Check for uncommitted changes
|
||||||
const { stdout: status } = await execAsync('git status --porcelain', {
|
const { stdout: status } = await execAsync('git status --porcelain', {
|
||||||
cwd: worktreePath,
|
cwd: worktreePath,
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* POST /list-branches endpoint - List all local branches
|
* 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';
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { getErrorMessage, logWorktreeError, isGitRepo, hasCommits } from '../common.js';
|
import { getErrorMessage, logWorktreeError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -30,26 +33,6 @@ export function createListBranchesHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the path is a git repository before running git commands
|
|
||||||
if (!(await isGitRepo(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Not a git repository',
|
|
||||||
code: 'NOT_GIT_REPO',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the repository has any commits (freshly init'd repos have no HEAD)
|
|
||||||
if (!(await hasCommits(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Repository has no commits yet',
|
|
||||||
code: 'NO_COMMITS',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current branch
|
// Get current branch
|
||||||
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
||||||
cwd: worktreePath,
|
cwd: worktreePath,
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* POST /merge endpoint - Merge feature (merge worktree branch into main)
|
* 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';
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -27,26 +30,6 @@ export function createMergeHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if path is a git repository
|
|
||||||
if (!(await isGitRepo(projectPath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Not a git repository',
|
|
||||||
code: 'NOT_GIT_REPO',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if repository has at least one commit
|
|
||||||
if (!(await hasCommits(projectPath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Repository has no commits yet',
|
|
||||||
code: 'NO_COMMITS',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const branchName = `feature/${featureId}`;
|
const branchName = `feature/${featureId}`;
|
||||||
// Git worktrees are stored in project directory
|
// Git worktrees are stored in project directory
|
||||||
const worktreePath = path.join(projectPath, '.worktrees', featureId);
|
const worktreePath = path.join(projectPath, '.worktrees', featureId);
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* POST /pull endpoint - Pull latest changes for a worktree/branch
|
* 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';
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -24,26 +27,6 @@ export function createPullHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if path is a git repository
|
|
||||||
if (!(await isGitRepo(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Not a git repository',
|
|
||||||
code: 'NOT_GIT_REPO',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if repository has at least one commit
|
|
||||||
if (!(await hasCommits(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Repository has no commits yet',
|
|
||||||
code: 'NO_COMMITS',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current branch name
|
// Get current branch name
|
||||||
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
||||||
cwd: worktreePath,
|
cwd: worktreePath,
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* POST /push endpoint - Push a worktree branch to remote
|
* 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';
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -25,26 +28,6 @@ export function createPushHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if path is a git repository
|
|
||||||
if (!(await isGitRepo(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Not a git repository',
|
|
||||||
code: 'NOT_GIT_REPO',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if repository has at least one commit
|
|
||||||
if (!(await hasCommits(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Repository has no commits yet',
|
|
||||||
code: 'NO_COMMITS',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get branch name
|
// Get branch name
|
||||||
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
||||||
cwd: worktreePath,
|
cwd: worktreePath,
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
* Simple branch switching.
|
* Simple branch switching.
|
||||||
* If there are uncommitted changes, the switch will fail and
|
* If there are uncommitted changes, the switch will fail and
|
||||||
* the user should commit first.
|
* 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';
|
import type { Request, Response } from 'express';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js';
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -83,26 +86,6 @@ export function createSwitchBranchHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if path is a git repository
|
|
||||||
if (!(await isGitRepo(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Not a git repository',
|
|
||||||
code: 'NOT_GIT_REPO',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if repository has at least one commit
|
|
||||||
if (!(await hasCommits(worktreePath))) {
|
|
||||||
res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Repository has no commits yet',
|
|
||||||
code: 'NO_COMMITS',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current branch
|
// Get current branch
|
||||||
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
||||||
cwd: worktreePath,
|
cwd: worktreePath,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
||||||
import {
|
import {
|
||||||
Trash2,
|
Trash2,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
@@ -25,6 +24,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import type { WorktreeInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
|
import type { WorktreeInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
|
||||||
|
import { TooltipWrapper } from './tooltip-wrapper';
|
||||||
|
|
||||||
interface WorktreeActionsDropdownProps {
|
interface WorktreeActionsDropdownProps {
|
||||||
worktree: WorktreeInfo;
|
worktree: WorktreeInfo;
|
||||||
@@ -146,92 +146,58 @@ export function WorktreeActionsDropdown({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<TooltipProvider>
|
<TooltipWrapper showTooltip={!!gitOpsDisabledReason} tooltipContent={gitOpsDisabledReason}>
|
||||||
<Tooltip>
|
<DropdownMenuItem
|
||||||
<TooltipTrigger asChild>
|
onClick={() => canPerformGitOps && onPull(worktree)}
|
||||||
<div>
|
disabled={isPulling || !canPerformGitOps}
|
||||||
<DropdownMenuItem
|
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
|
||||||
onClick={() => canPerformGitOps && onPull(worktree)}
|
>
|
||||||
disabled={isPulling || !canPerformGitOps}
|
<Download className={cn('w-3.5 h-3.5 mr-2', isPulling && 'animate-pulse')} />
|
||||||
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
|
{isPulling ? 'Pulling...' : 'Pull'}
|
||||||
>
|
{!canPerformGitOps && <AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />}
|
||||||
<Download className={cn('w-3.5 h-3.5 mr-2', isPulling && 'animate-pulse')} />
|
{canPerformGitOps && behindCount > 0 && (
|
||||||
{isPulling ? 'Pulling...' : 'Pull'}
|
<span className="ml-auto text-[10px] bg-muted px-1.5 py-0.5 rounded">
|
||||||
{!canPerformGitOps && (
|
{behindCount} behind
|
||||||
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
|
</span>
|
||||||
)}
|
|
||||||
{canPerformGitOps && behindCount > 0 && (
|
|
||||||
<span className="ml-auto text-[10px] bg-muted px-1.5 py-0.5 rounded">
|
|
||||||
{behindCount} behind
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{gitOpsDisabledReason && (
|
|
||||||
<TooltipContent side="left">
|
|
||||||
<p>{gitOpsDisabledReason}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</DropdownMenuItem>
|
||||||
</TooltipProvider>
|
</TooltipWrapper>
|
||||||
<TooltipProvider>
|
<TooltipWrapper showTooltip={!!gitOpsDisabledReason} tooltipContent={gitOpsDisabledReason}>
|
||||||
<Tooltip>
|
<DropdownMenuItem
|
||||||
<TooltipTrigger asChild>
|
onClick={() => canPerformGitOps && onPush(worktree)}
|
||||||
<div>
|
disabled={isPushing || aheadCount === 0 || !canPerformGitOps}
|
||||||
<DropdownMenuItem
|
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
|
||||||
onClick={() => canPerformGitOps && onPush(worktree)}
|
>
|
||||||
disabled={isPushing || aheadCount === 0 || !canPerformGitOps}
|
<Upload className={cn('w-3.5 h-3.5 mr-2', isPushing && 'animate-pulse')} />
|
||||||
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
|
{isPushing ? 'Pushing...' : 'Push'}
|
||||||
>
|
{!canPerformGitOps && <AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />}
|
||||||
<Upload className={cn('w-3.5 h-3.5 mr-2', isPushing && 'animate-pulse')} />
|
{canPerformGitOps && aheadCount > 0 && (
|
||||||
{isPushing ? 'Pushing...' : 'Push'}
|
<span className="ml-auto text-[10px] bg-primary/20 text-primary px-1.5 py-0.5 rounded">
|
||||||
{!canPerformGitOps && (
|
{aheadCount} ahead
|
||||||
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
|
</span>
|
||||||
)}
|
|
||||||
{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>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{gitOpsDisabledReason && (
|
|
||||||
<TooltipContent side="left">
|
|
||||||
<p>{gitOpsDisabledReason}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</DropdownMenuItem>
|
||||||
</TooltipProvider>
|
</TooltipWrapper>
|
||||||
{!worktree.isMain && (
|
{!worktree.isMain && (
|
||||||
<TooltipProvider>
|
<TooltipWrapper
|
||||||
<Tooltip>
|
showTooltip={!!gitOpsDisabledReason}
|
||||||
<TooltipTrigger asChild>
|
tooltipContent={gitOpsDisabledReason}
|
||||||
<div>
|
>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => canPerformGitOps && onResolveConflicts(worktree)}
|
onClick={() => canPerformGitOps && onResolveConflicts(worktree)}
|
||||||
disabled={!canPerformGitOps}
|
disabled={!canPerformGitOps}
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-xs text-purple-500 focus:text-purple-600',
|
'text-xs text-purple-500 focus:text-purple-600',
|
||||||
!canPerformGitOps && 'opacity-50 cursor-not-allowed'
|
!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>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{gitOpsDisabledReason && (
|
|
||||||
<TooltipContent side="left">
|
|
||||||
<p>{gitOpsDisabledReason}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
>
|
||||||
</TooltipProvider>
|
<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 />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={() => onOpenInEditor(worktree)} className="text-xs">
|
<DropdownMenuItem onClick={() => onOpenInEditor(worktree)} className="text-xs">
|
||||||
@@ -240,60 +206,41 @@ export function WorktreeActionsDropdown({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{worktree.hasChanges && (
|
{worktree.hasChanges && (
|
||||||
<TooltipProvider>
|
<TooltipWrapper
|
||||||
<Tooltip>
|
showTooltip={!gitRepoStatus.isGitRepo}
|
||||||
<TooltipTrigger asChild>
|
tooltipContent="Not a git repository"
|
||||||
<div>
|
>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => gitRepoStatus.isGitRepo && onCommit(worktree)}
|
onClick={() => gitRepoStatus.isGitRepo && onCommit(worktree)}
|
||||||
disabled={!gitRepoStatus.isGitRepo}
|
disabled={!gitRepoStatus.isGitRepo}
|
||||||
className={cn(
|
className={cn('text-xs', !gitRepoStatus.isGitRepo && 'opacity-50 cursor-not-allowed')}
|
||||||
'text-xs',
|
>
|
||||||
!gitRepoStatus.isGitRepo && 'opacity-50 cursor-not-allowed'
|
<GitCommit className="w-3.5 h-3.5 mr-2" />
|
||||||
)}
|
Commit Changes
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{!gitRepoStatus.isGitRepo && (
|
{!gitRepoStatus.isGitRepo && (
|
||||||
<TooltipContent side="left">
|
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
|
||||||
<p>Not a git repository</p>
|
|
||||||
</TooltipContent>
|
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</DropdownMenuItem>
|
||||||
</TooltipProvider>
|
</TooltipWrapper>
|
||||||
)}
|
)}
|
||||||
{/* Show PR option for non-primary worktrees, or primary worktree with changes */}
|
{/* Show PR option for non-primary worktrees, or primary worktree with changes */}
|
||||||
{(!worktree.isMain || worktree.hasChanges) && !hasPR && (
|
{(!worktree.isMain || worktree.hasChanges) && !hasPR && (
|
||||||
<TooltipProvider>
|
<TooltipWrapper
|
||||||
<Tooltip>
|
showTooltip={!!gitOpsDisabledReason}
|
||||||
<TooltipTrigger asChild>
|
tooltipContent={gitOpsDisabledReason}
|
||||||
<div>
|
>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => canPerformGitOps && onCreatePR(worktree)}
|
onClick={() => canPerformGitOps && onCreatePR(worktree)}
|
||||||
disabled={!canPerformGitOps}
|
disabled={!canPerformGitOps}
|
||||||
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
|
className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')}
|
||||||
>
|
>
|
||||||
<GitPullRequest className="w-3.5 h-3.5 mr-2" />
|
<GitPullRequest className="w-3.5 h-3.5 mr-2" />
|
||||||
Create Pull Request
|
Create Pull Request
|
||||||
{!canPerformGitOps && (
|
{!canPerformGitOps && (
|
||||||
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
|
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{gitOpsDisabledReason && (
|
|
||||||
<TooltipContent side="left">
|
|
||||||
<p>{gitOpsDisabledReason}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</DropdownMenuItem>
|
||||||
</TooltipProvider>
|
</TooltipWrapper>
|
||||||
)}
|
)}
|
||||||
{/* Show PR info and Address Comments button if PR exists */}
|
{/* Show PR info and Address Comments button if PR exists */}
|
||||||
{!worktree.isMain && hasPR && worktree.pr && (
|
{!worktree.isMain && hasPR && worktree.pr && (
|
||||||
|
|||||||
@@ -13,49 +13,53 @@ export function useBranches() {
|
|||||||
hasCommits: true,
|
hasCommits: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchBranches = useCallback(async (worktreePath: string) => {
|
/** Helper to reset branch state to initial values */
|
||||||
setIsLoadingBranches(true);
|
const resetBranchState = useCallback(() => {
|
||||||
try {
|
setBranches([]);
|
||||||
const api = getElectronAPI();
|
setAheadCount(0);
|
||||||
if (!api?.worktree?.listBranches) {
|
setBehindCount(0);
|
||||||
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
|
|
||||||
setBranches([]);
|
|
||||||
setAheadCount(0);
|
|
||||||
setBehindCount(0);
|
|
||||||
setGitRepoStatus({ isGitRepo: false, hasCommits: false });
|
|
||||||
} else if (result.code === 'NO_COMMITS') {
|
|
||||||
// Git repo but no commits yet - clear branches silently without logging an error
|
|
||||||
setBranches([]);
|
|
||||||
setAheadCount(0);
|
|
||||||
setBehindCount(0);
|
|
||||||
setGitRepoStatus({ isGitRepo: true, hasCommits: false });
|
|
||||||
} else if (!result.success) {
|
|
||||||
// Other errors - log them
|
|
||||||
console.warn('Failed to fetch branches:', result.error);
|
|
||||||
setBranches([]);
|
|
||||||
setAheadCount(0);
|
|
||||||
setBehindCount(0);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch branches:', error);
|
|
||||||
setBranches([]);
|
|
||||||
setAheadCount(0);
|
|
||||||
setBehindCount(0);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingBranches(false);
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
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(() => {
|
const resetBranchFilter = useCallback(() => {
|
||||||
setBranchFilter('');
|
setBranchFilter('');
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -5,13 +5,27 @@ import type { WorktreeInfo } from '../types';
|
|||||||
|
|
||||||
// Error codes that need special user-friendly handling
|
// Error codes that need special user-friendly handling
|
||||||
const GIT_STATUS_ERROR_CODES = ['NOT_GIT_REPO', 'NO_COMMITS'] as const;
|
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
|
// User-friendly messages for git status errors
|
||||||
const GIT_STATUS_ERROR_MESSAGES: Record<string, string> = {
|
const GIT_STATUS_ERROR_MESSAGES: Record<GitStatusErrorCode, string> = {
|
||||||
NOT_GIT_REPO: 'This directory is not a git repository',
|
NOT_GIT_REPO: 'This directory is not a git repository',
|
||||||
NO_COMMITS: 'Repository has no commits yet. Create an initial commit first.',
|
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 {
|
interface UseWorktreeActionsOptions {
|
||||||
fetchWorktrees: () => Promise<Array<{ path: string; branch: string }> | undefined>;
|
fetchWorktrees: () => Promise<Array<{ path: string; branch: string }> | undefined>;
|
||||||
fetchBranches: (worktreePath: string) => Promise<void>;
|
fetchBranches: (worktreePath: string) => Promise<void>;
|
||||||
@@ -38,15 +52,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
|
|||||||
toast.success(result.result.message);
|
toast.success(result.result.message);
|
||||||
fetchWorktrees();
|
fetchWorktrees();
|
||||||
} else {
|
} else {
|
||||||
// Handle git status errors with informative messages
|
if (handleGitStatusError(result)) return;
|
||||||
const errorCode = (result as { code?: string }).code;
|
|
||||||
if (
|
|
||||||
errorCode &&
|
|
||||||
GIT_STATUS_ERROR_CODES.includes(errorCode as (typeof GIT_STATUS_ERROR_CODES)[number])
|
|
||||||
) {
|
|
||||||
toast.info(GIT_STATUS_ERROR_MESSAGES[errorCode] || result.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toast.error(result.error || 'Failed to switch branch');
|
toast.error(result.error || 'Failed to switch branch');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -74,15 +80,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
|
|||||||
toast.success(result.result.message);
|
toast.success(result.result.message);
|
||||||
fetchWorktrees();
|
fetchWorktrees();
|
||||||
} else {
|
} else {
|
||||||
// Handle git status errors with informative messages
|
if (handleGitStatusError(result)) return;
|
||||||
const errorCode = (result as { code?: string }).code;
|
|
||||||
if (
|
|
||||||
errorCode &&
|
|
||||||
GIT_STATUS_ERROR_CODES.includes(errorCode as (typeof GIT_STATUS_ERROR_CODES)[number])
|
|
||||||
) {
|
|
||||||
toast.info(GIT_STATUS_ERROR_MESSAGES[errorCode] || result.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toast.error(result.error || 'Failed to pull latest changes');
|
toast.error(result.error || 'Failed to pull latest changes');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -111,15 +109,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
|
|||||||
fetchBranches(worktree.path);
|
fetchBranches(worktree.path);
|
||||||
fetchWorktrees();
|
fetchWorktrees();
|
||||||
} else {
|
} else {
|
||||||
// Handle git status errors with informative messages
|
if (handleGitStatusError(result)) return;
|
||||||
const errorCode = (result as { code?: string }).code;
|
|
||||||
if (
|
|
||||||
errorCode &&
|
|
||||||
GIT_STATUS_ERROR_CODES.includes(errorCode as (typeof GIT_STATUS_ERROR_CODES)[number])
|
|
||||||
) {
|
|
||||||
toast.info(GIT_STATUS_ERROR_MESSAGES[errorCode] || result.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toast.error(result.error || 'Failed to push changes');
|
toast.error(result.error || 'Failed to push changes');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
6
apps/ui/src/types/electron.d.ts
vendored
6
apps/ui/src/types/electron.d.ts
vendored
@@ -733,6 +733,7 @@ export interface WorktreeAPI {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
|
code?: 'NOT_GIT_REPO' | 'NO_COMMITS';
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Create a pull request from a worktree
|
// Create a pull request from a worktree
|
||||||
@@ -783,6 +784,7 @@ export interface WorktreeAPI {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
|
code?: 'NOT_GIT_REPO' | 'NO_COMMITS';
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Create and checkout a new branch
|
// Create and checkout a new branch
|
||||||
@@ -797,6 +799,7 @@ export interface WorktreeAPI {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
|
code?: 'NOT_GIT_REPO' | 'NO_COMMITS';
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// List all local branches
|
// List all local branches
|
||||||
@@ -813,7 +816,7 @@ export interface WorktreeAPI {
|
|||||||
behindCount: number;
|
behindCount: number;
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
code?: 'NOT_GIT_REPO'; // Error code for non-git directories
|
code?: 'NOT_GIT_REPO' | 'NO_COMMITS'; // Error codes for git status issues
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Switch to an existing branch
|
// Switch to an existing branch
|
||||||
@@ -828,6 +831,7 @@ export interface WorktreeAPI {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
|
code?: 'NOT_GIT_REPO' | 'NO_COMMITS' | 'UNCOMMITTED_CHANGES';
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Open a worktree directory in the editor
|
// Open a worktree directory in the editor
|
||||||
|
|||||||
Reference in New Issue
Block a user