mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
Merge pull request #492 from AutoMaker-Org/feature/v0.11.0rc-1768413895104-31pa
feat: merge worktree to main in dropdown menu
This commit is contained in:
@@ -8,7 +8,6 @@
|
|||||||
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 { getErrorMessage, logError } from '../common.js';
|
import { getErrorMessage, logError } from '../common.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
@@ -16,28 +15,31 @@ const execAsync = promisify(exec);
|
|||||||
export function createMergeHandler() {
|
export function createMergeHandler() {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { projectPath, featureId, options } = req.body as {
|
const { projectPath, branchName, worktreePath, options } = req.body as {
|
||||||
projectPath: string;
|
projectPath: string;
|
||||||
featureId: string;
|
branchName: string;
|
||||||
|
worktreePath: string;
|
||||||
options?: { squash?: boolean; message?: string };
|
options?: { squash?: boolean; message?: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath || !featureId) {
|
if (!projectPath || !branchName || !worktreePath) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'projectPath and featureId required',
|
error: 'projectPath, branchName, and worktreePath are required',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const branchName = `feature/${featureId}`;
|
// Validate branch exists
|
||||||
// Git worktrees are stored in project directory
|
try {
|
||||||
const worktreePath = path.join(projectPath, '.worktrees', featureId);
|
await execAsync(`git rev-parse --verify ${branchName}`, { cwd: projectPath });
|
||||||
|
} catch {
|
||||||
// Get current branch
|
res.status(400).json({
|
||||||
const { stdout: currentBranch } = await execAsync('git rev-parse --abbrev-ref HEAD', {
|
success: false,
|
||||||
cwd: projectPath,
|
error: `Branch "${branchName}" does not exist`,
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Merge the feature branch
|
// Merge the feature branch
|
||||||
const mergeCmd = options?.squash
|
const mergeCmd = options?.squash
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import { DeleteWorktreeDialog } from './board-view/dialogs/delete-worktree-dialo
|
|||||||
import { CommitWorktreeDialog } from './board-view/dialogs/commit-worktree-dialog';
|
import { CommitWorktreeDialog } from './board-view/dialogs/commit-worktree-dialog';
|
||||||
import { CreatePRDialog } from './board-view/dialogs/create-pr-dialog';
|
import { CreatePRDialog } from './board-view/dialogs/create-pr-dialog';
|
||||||
import { CreateBranchDialog } from './board-view/dialogs/create-branch-dialog';
|
import { CreateBranchDialog } from './board-view/dialogs/create-branch-dialog';
|
||||||
|
import { MergeWorktreeDialog } from './board-view/dialogs/merge-worktree-dialog';
|
||||||
import { WorktreePanel } from './board-view/worktree-panel';
|
import { WorktreePanel } from './board-view/worktree-panel';
|
||||||
import type { PRInfo, WorktreeInfo } from './board-view/worktree-panel/types';
|
import type { PRInfo, WorktreeInfo } from './board-view/worktree-panel/types';
|
||||||
import { COLUMNS, getColumnsWithPipeline } from './board-view/constants';
|
import { COLUMNS, getColumnsWithPipeline } from './board-view/constants';
|
||||||
@@ -148,6 +149,7 @@ export function BoardView() {
|
|||||||
const [showCommitWorktreeDialog, setShowCommitWorktreeDialog] = useState(false);
|
const [showCommitWorktreeDialog, setShowCommitWorktreeDialog] = useState(false);
|
||||||
const [showCreatePRDialog, setShowCreatePRDialog] = useState(false);
|
const [showCreatePRDialog, setShowCreatePRDialog] = useState(false);
|
||||||
const [showCreateBranchDialog, setShowCreateBranchDialog] = useState(false);
|
const [showCreateBranchDialog, setShowCreateBranchDialog] = useState(false);
|
||||||
|
const [showMergeWorktreeDialog, setShowMergeWorktreeDialog] = useState(false);
|
||||||
const [selectedWorktreeForAction, setSelectedWorktreeForAction] = useState<{
|
const [selectedWorktreeForAction, setSelectedWorktreeForAction] = useState<{
|
||||||
path: string;
|
path: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
@@ -1354,6 +1356,10 @@ export function BoardView() {
|
|||||||
}}
|
}}
|
||||||
onAddressPRComments={handleAddressPRComments}
|
onAddressPRComments={handleAddressPRComments}
|
||||||
onResolveConflicts={handleResolveConflicts}
|
onResolveConflicts={handleResolveConflicts}
|
||||||
|
onMerge={(worktree) => {
|
||||||
|
setSelectedWorktreeForAction(worktree);
|
||||||
|
setShowMergeWorktreeDialog(true);
|
||||||
|
}}
|
||||||
onRemovedWorktrees={handleRemovedWorktrees}
|
onRemovedWorktrees={handleRemovedWorktrees}
|
||||||
runningFeatureIds={runningAutoTasks}
|
runningFeatureIds={runningAutoTasks}
|
||||||
branchCardCounts={branchCardCounts}
|
branchCardCounts={branchCardCounts}
|
||||||
@@ -1698,6 +1704,35 @@ export function BoardView() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Merge Worktree Dialog */}
|
||||||
|
<MergeWorktreeDialog
|
||||||
|
open={showMergeWorktreeDialog}
|
||||||
|
onOpenChange={setShowMergeWorktreeDialog}
|
||||||
|
projectPath={currentProject.path}
|
||||||
|
worktree={selectedWorktreeForAction}
|
||||||
|
affectedFeatureCount={
|
||||||
|
selectedWorktreeForAction
|
||||||
|
? hookFeatures.filter((f) => f.branchName === selectedWorktreeForAction.branch).length
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
onMerged={(mergedWorktree) => {
|
||||||
|
// Reset features that were assigned to the merged worktree (by branch)
|
||||||
|
hookFeatures.forEach((feature) => {
|
||||||
|
if (feature.branchName === mergedWorktree.branch) {
|
||||||
|
// Reset the feature's branch assignment - update both local state and persist
|
||||||
|
const updates = {
|
||||||
|
branchName: null as unknown as string | undefined,
|
||||||
|
};
|
||||||
|
updateFeature(feature.id, updates);
|
||||||
|
persistFeatureUpdate(feature.id, updates);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setWorktreeRefreshKey((k) => k + 1);
|
||||||
|
setSelectedWorktreeForAction(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Commit Worktree Dialog */}
|
{/* Commit Worktree Dialog */}
|
||||||
<CommitWorktreeDialog
|
<CommitWorktreeDialog
|
||||||
open={showCommitWorktreeDialog}
|
open={showCommitWorktreeDialog}
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Loader2, GitMerge, AlertTriangle, CheckCircle2 } from 'lucide-react';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface WorktreeInfo {
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
isMain: boolean;
|
||||||
|
hasChanges?: boolean;
|
||||||
|
changedFilesCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MergeWorktreeDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
projectPath: string;
|
||||||
|
worktree: WorktreeInfo | null;
|
||||||
|
onMerged: (mergedWorktree: WorktreeInfo) => void;
|
||||||
|
/** Number of features assigned to this worktree's branch */
|
||||||
|
affectedFeatureCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialogStep = 'confirm' | 'verify';
|
||||||
|
|
||||||
|
export function MergeWorktreeDialog({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
projectPath,
|
||||||
|
worktree,
|
||||||
|
onMerged,
|
||||||
|
affectedFeatureCount = 0,
|
||||||
|
}: MergeWorktreeDialogProps) {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [step, setStep] = useState<DialogStep>('confirm');
|
||||||
|
const [confirmText, setConfirmText] = useState('');
|
||||||
|
|
||||||
|
// Reset state when dialog opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setIsLoading(false);
|
||||||
|
setStep('confirm');
|
||||||
|
setConfirmText('');
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleProceedToVerify = () => {
|
||||||
|
setStep('verify');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMerge = async () => {
|
||||||
|
if (!worktree) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
if (!api?.worktree?.mergeFeature) {
|
||||||
|
toast.error('Worktree API not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass branchName and worktreePath directly to the API
|
||||||
|
const result = await api.worktree.mergeFeature(projectPath, worktree.branch, worktree.path);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success('Branch merged to main', {
|
||||||
|
description: `Branch "${worktree.branch}" has been merged and cleaned up`,
|
||||||
|
});
|
||||||
|
onMerged(worktree);
|
||||||
|
onOpenChange(false);
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to merge branch', {
|
||||||
|
description: result.error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Failed to merge branch', {
|
||||||
|
description: err instanceof Error ? err.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!worktree) return null;
|
||||||
|
|
||||||
|
const confirmationWord = 'merge';
|
||||||
|
const isConfirmValid = confirmText.toLowerCase() === confirmationWord;
|
||||||
|
|
||||||
|
// First step: Show what will happen and ask for confirmation
|
||||||
|
if (step === 'confirm') {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<GitMerge className="w-5 h-5 text-green-600" />
|
||||||
|
Merge to Main
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription asChild>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<span className="block">
|
||||||
|
Merge branch{' '}
|
||||||
|
<code className="font-mono bg-muted px-1 rounded">{worktree.branch}</code> into
|
||||||
|
main?
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="text-sm text-muted-foreground mt-2">
|
||||||
|
This will:
|
||||||
|
<ul className="list-disc list-inside mt-1 space-y-1">
|
||||||
|
<li>Merge the branch into the main branch</li>
|
||||||
|
<li>Remove the worktree directory</li>
|
||||||
|
<li>Delete the branch</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{worktree.hasChanges && (
|
||||||
|
<div className="flex items-start gap-2 p-3 rounded-md bg-yellow-500/10 border border-yellow-500/20 mt-2">
|
||||||
|
<AlertTriangle className="w-4 h-4 text-yellow-500 mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-yellow-500 text-sm">
|
||||||
|
This worktree has {worktree.changedFilesCount} uncommitted change(s). Please
|
||||||
|
commit or discard them before merging.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{affectedFeatureCount > 0 && (
|
||||||
|
<div className="flex items-start gap-2 p-3 rounded-md bg-blue-500/10 border border-blue-500/20 mt-2">
|
||||||
|
<AlertTriangle className="w-4 h-4 text-blue-500 mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-blue-500 text-sm">
|
||||||
|
{affectedFeatureCount} feature{affectedFeatureCount !== 1 ? 's' : ''}{' '}
|
||||||
|
{affectedFeatureCount !== 1 ? 'are' : 'is'} assigned to this branch and will
|
||||||
|
be unassigned after merge.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleProceedToVerify}
|
||||||
|
disabled={worktree.hasChanges}
|
||||||
|
className="bg-green-600 hover:bg-green-700 text-white"
|
||||||
|
>
|
||||||
|
<GitMerge className="w-4 h-4 mr-2" />
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second step: Type confirmation
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<AlertTriangle className="w-5 h-5 text-orange-500" />
|
||||||
|
Confirm Merge
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription asChild>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-start gap-2 p-3 rounded-md bg-orange-500/10 border border-orange-500/20">
|
||||||
|
<AlertTriangle className="w-4 h-4 text-orange-500 mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-orange-600 dark:text-orange-400 text-sm">
|
||||||
|
This action cannot be undone. The branch{' '}
|
||||||
|
<code className="font-mono bg-muted px-1 rounded">{worktree.branch}</code> will be
|
||||||
|
permanently deleted after merging.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="confirm-merge" className="text-sm text-foreground">
|
||||||
|
Type <span className="font-bold text-foreground">{confirmationWord}</span> to
|
||||||
|
confirm:
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="confirm-merge"
|
||||||
|
value={confirmText}
|
||||||
|
onChange={(e) => setConfirmText(e.target.value)}
|
||||||
|
placeholder={confirmationWord}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="font-mono"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="ghost" onClick={() => setStep('confirm')} disabled={isLoading}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleMerge}
|
||||||
|
disabled={isLoading || !isConfirmValid}
|
||||||
|
className="bg-green-600 hover:bg-green-700 text-white"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
Merging...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CheckCircle2 className="w-4 h-4 mr-2" />
|
||||||
|
Merge to Main
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -55,6 +55,7 @@ interface WorktreeActionsDropdownProps {
|
|||||||
onCreatePR: (worktree: WorktreeInfo) => void;
|
onCreatePR: (worktree: WorktreeInfo) => void;
|
||||||
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
|
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
|
||||||
onResolveConflicts: (worktree: WorktreeInfo) => void;
|
onResolveConflicts: (worktree: WorktreeInfo) => void;
|
||||||
|
onMerge: (worktree: WorktreeInfo) => void;
|
||||||
onDeleteWorktree: (worktree: WorktreeInfo) => void;
|
onDeleteWorktree: (worktree: WorktreeInfo) => void;
|
||||||
onStartDevServer: (worktree: WorktreeInfo) => void;
|
onStartDevServer: (worktree: WorktreeInfo) => void;
|
||||||
onStopDevServer: (worktree: WorktreeInfo) => void;
|
onStopDevServer: (worktree: WorktreeInfo) => void;
|
||||||
@@ -84,6 +85,7 @@ export function WorktreeActionsDropdown({
|
|||||||
onCreatePR,
|
onCreatePR,
|
||||||
onAddressPRComments,
|
onAddressPRComments,
|
||||||
onResolveConflicts,
|
onResolveConflicts,
|
||||||
|
onMerge,
|
||||||
onDeleteWorktree,
|
onDeleteWorktree,
|
||||||
onStartDevServer,
|
onStartDevServer,
|
||||||
onStopDevServer,
|
onStopDevServer,
|
||||||
@@ -231,6 +233,27 @@ export function WorktreeActionsDropdown({
|
|||||||
{!canPerformGitOps && <AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />}
|
{!canPerformGitOps && <AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
|
{!worktree.isMain && (
|
||||||
|
<TooltipWrapper
|
||||||
|
showTooltip={!!gitOpsDisabledReason}
|
||||||
|
tooltipContent={gitOpsDisabledReason}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => canPerformGitOps && onMerge(worktree)}
|
||||||
|
disabled={!canPerformGitOps}
|
||||||
|
className={cn(
|
||||||
|
'text-xs text-green-600 focus:text-green-700',
|
||||||
|
!canPerformGitOps && 'opacity-50 cursor-not-allowed'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<GitMerge className="w-3.5 h-3.5 mr-2" />
|
||||||
|
Merge to Main
|
||||||
|
{!canPerformGitOps && (
|
||||||
|
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</TooltipWrapper>
|
||||||
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{/* Open in editor - split button: click main area for default, chevron for other options */}
|
{/* Open in editor - split button: click main area for default, chevron for other options */}
|
||||||
{effectiveDefaultEditor && (
|
{effectiveDefaultEditor && (
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ interface WorktreeTabProps {
|
|||||||
onCreatePR: (worktree: WorktreeInfo) => void;
|
onCreatePR: (worktree: WorktreeInfo) => void;
|
||||||
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
|
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
|
||||||
onResolveConflicts: (worktree: WorktreeInfo) => void;
|
onResolveConflicts: (worktree: WorktreeInfo) => void;
|
||||||
|
onMerge: (worktree: WorktreeInfo) => void;
|
||||||
onDeleteWorktree: (worktree: WorktreeInfo) => void;
|
onDeleteWorktree: (worktree: WorktreeInfo) => void;
|
||||||
onStartDevServer: (worktree: WorktreeInfo) => void;
|
onStartDevServer: (worktree: WorktreeInfo) => void;
|
||||||
onStopDevServer: (worktree: WorktreeInfo) => void;
|
onStopDevServer: (worktree: WorktreeInfo) => void;
|
||||||
@@ -84,6 +85,7 @@ export function WorktreeTab({
|
|||||||
onCreatePR,
|
onCreatePR,
|
||||||
onAddressPRComments,
|
onAddressPRComments,
|
||||||
onResolveConflicts,
|
onResolveConflicts,
|
||||||
|
onMerge,
|
||||||
onDeleteWorktree,
|
onDeleteWorktree,
|
||||||
onStartDevServer,
|
onStartDevServer,
|
||||||
onStopDevServer,
|
onStopDevServer,
|
||||||
@@ -344,6 +346,7 @@ export function WorktreeTab({
|
|||||||
onCreatePR={onCreatePR}
|
onCreatePR={onCreatePR}
|
||||||
onAddressPRComments={onAddressPRComments}
|
onAddressPRComments={onAddressPRComments}
|
||||||
onResolveConflicts={onResolveConflicts}
|
onResolveConflicts={onResolveConflicts}
|
||||||
|
onMerge={onMerge}
|
||||||
onDeleteWorktree={onDeleteWorktree}
|
onDeleteWorktree={onDeleteWorktree}
|
||||||
onStartDevServer={onStartDevServer}
|
onStartDevServer={onStartDevServer}
|
||||||
onStopDevServer={onStopDevServer}
|
onStopDevServer={onStopDevServer}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export interface WorktreePanelProps {
|
|||||||
onCreateBranch: (worktree: WorktreeInfo) => void;
|
onCreateBranch: (worktree: WorktreeInfo) => void;
|
||||||
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
|
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
|
||||||
onResolveConflicts: (worktree: WorktreeInfo) => void;
|
onResolveConflicts: (worktree: WorktreeInfo) => void;
|
||||||
|
onMerge: (worktree: WorktreeInfo) => void;
|
||||||
onRemovedWorktrees?: (removedWorktrees: Array<{ path: string; branch: string }>) => void;
|
onRemovedWorktrees?: (removedWorktrees: Array<{ path: string; branch: string }>) => void;
|
||||||
runningFeatureIds?: string[];
|
runningFeatureIds?: string[];
|
||||||
features?: FeatureInfo[];
|
features?: FeatureInfo[];
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export function WorktreePanel({
|
|||||||
onCreateBranch,
|
onCreateBranch,
|
||||||
onAddressPRComments,
|
onAddressPRComments,
|
||||||
onResolveConflicts,
|
onResolveConflicts,
|
||||||
|
onMerge,
|
||||||
onRemovedWorktrees,
|
onRemovedWorktrees,
|
||||||
runningFeatureIds = [],
|
runningFeatureIds = [],
|
||||||
features = [],
|
features = [],
|
||||||
@@ -248,10 +249,12 @@ export function WorktreePanel({
|
|||||||
onCreatePR={onCreatePR}
|
onCreatePR={onCreatePR}
|
||||||
onAddressPRComments={onAddressPRComments}
|
onAddressPRComments={onAddressPRComments}
|
||||||
onResolveConflicts={onResolveConflicts}
|
onResolveConflicts={onResolveConflicts}
|
||||||
|
onMerge={onMerge}
|
||||||
onDeleteWorktree={onDeleteWorktree}
|
onDeleteWorktree={onDeleteWorktree}
|
||||||
onStartDevServer={handleStartDevServer}
|
onStartDevServer={handleStartDevServer}
|
||||||
onStopDevServer={handleStopDevServer}
|
onStopDevServer={handleStopDevServer}
|
||||||
onOpenDevServerUrl={handleOpenDevServerUrl}
|
onOpenDevServerUrl={handleOpenDevServerUrl}
|
||||||
|
onViewDevServerLogs={handleViewDevServerLogs}
|
||||||
onRunInitScript={handleRunInitScript}
|
onRunInitScript={handleRunInitScript}
|
||||||
hasInitScript={hasInitScript}
|
hasInitScript={hasInitScript}
|
||||||
/>
|
/>
|
||||||
@@ -333,6 +336,7 @@ export function WorktreePanel({
|
|||||||
onCreatePR={onCreatePR}
|
onCreatePR={onCreatePR}
|
||||||
onAddressPRComments={onAddressPRComments}
|
onAddressPRComments={onAddressPRComments}
|
||||||
onResolveConflicts={onResolveConflicts}
|
onResolveConflicts={onResolveConflicts}
|
||||||
|
onMerge={onMerge}
|
||||||
onDeleteWorktree={onDeleteWorktree}
|
onDeleteWorktree={onDeleteWorktree}
|
||||||
onStartDevServer={handleStartDevServer}
|
onStartDevServer={handleStartDevServer}
|
||||||
onStopDevServer={handleStopDevServer}
|
onStopDevServer={handleStopDevServer}
|
||||||
@@ -390,6 +394,7 @@ export function WorktreePanel({
|
|||||||
onCreatePR={onCreatePR}
|
onCreatePR={onCreatePR}
|
||||||
onAddressPRComments={onAddressPRComments}
|
onAddressPRComments={onAddressPRComments}
|
||||||
onResolveConflicts={onResolveConflicts}
|
onResolveConflicts={onResolveConflicts}
|
||||||
|
onMerge={onMerge}
|
||||||
onDeleteWorktree={onDeleteWorktree}
|
onDeleteWorktree={onDeleteWorktree}
|
||||||
onStartDevServer={handleStartDevServer}
|
onStartDevServer={handleStartDevServer}
|
||||||
onStopDevServer={handleStopDevServer}
|
onStopDevServer={handleStopDevServer}
|
||||||
|
|||||||
@@ -1440,13 +1440,19 @@ function createMockSetupAPI(): SetupAPI {
|
|||||||
// Mock Worktree API implementation
|
// Mock Worktree API implementation
|
||||||
function createMockWorktreeAPI(): WorktreeAPI {
|
function createMockWorktreeAPI(): WorktreeAPI {
|
||||||
return {
|
return {
|
||||||
mergeFeature: async (projectPath: string, featureId: string, options?: object) => {
|
mergeFeature: async (
|
||||||
|
projectPath: string,
|
||||||
|
branchName: string,
|
||||||
|
worktreePath: string,
|
||||||
|
options?: object
|
||||||
|
) => {
|
||||||
console.log('[Mock] Merging feature:', {
|
console.log('[Mock] Merging feature:', {
|
||||||
projectPath,
|
projectPath,
|
||||||
featureId,
|
branchName,
|
||||||
|
worktreePath,
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
return { success: true, mergedBranch: `feature/${featureId}` };
|
return { success: true, mergedBranch: branchName };
|
||||||
},
|
},
|
||||||
|
|
||||||
getInfo: async (projectPath: string, featureId: string) => {
|
getInfo: async (projectPath: string, featureId: string) => {
|
||||||
|
|||||||
@@ -1706,8 +1706,12 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
|
|
||||||
// Worktree API
|
// Worktree API
|
||||||
worktree: WorktreeAPI = {
|
worktree: WorktreeAPI = {
|
||||||
mergeFeature: (projectPath: string, featureId: string, options?: object) =>
|
mergeFeature: (
|
||||||
this.post('/api/worktree/merge', { projectPath, featureId, options }),
|
projectPath: string,
|
||||||
|
branchName: string,
|
||||||
|
worktreePath: string,
|
||||||
|
options?: object
|
||||||
|
) => this.post('/api/worktree/merge', { projectPath, branchName, worktreePath, options }),
|
||||||
getInfo: (projectPath: string, featureId: string) =>
|
getInfo: (projectPath: string, featureId: string) =>
|
||||||
this.post('/api/worktree/info', { projectPath, featureId }),
|
this.post('/api/worktree/info', { projectPath, featureId }),
|
||||||
getStatus: (projectPath: string, featureId: string) =>
|
getStatus: (projectPath: string, featureId: string) =>
|
||||||
|
|||||||
8
apps/ui/src/types/electron.d.ts
vendored
8
apps/ui/src/types/electron.d.ts
vendored
@@ -660,14 +660,14 @@ export interface FileDiffResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface WorktreeAPI {
|
export interface WorktreeAPI {
|
||||||
// Merge feature worktree changes back to main branch
|
// Merge worktree branch into main and clean up
|
||||||
mergeFeature: (
|
mergeFeature: (
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
featureId: string,
|
branchName: string,
|
||||||
|
worktreePath: string,
|
||||||
options?: {
|
options?: {
|
||||||
squash?: boolean;
|
squash?: boolean;
|
||||||
commitMessage?: string;
|
message?: string;
|
||||||
squashMessage?: string;
|
|
||||||
}
|
}
|
||||||
) => Promise<{
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user