mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-18 22:33:08 +00:00
Fix orphaned features when deleting worktrees (#820)
* Changes from fix/orphaned-features * fix: Handle feature migration failures and improve UI accessibility * feat: Add event emission for worktree deletion and feature migration * fix: Handle OpenCode model errors and prevent duplicate model IDs * feat: Add summary dialog and async verify with loading state * fix: Add type attributes to buttons and improve OpenCode model selection * fix: Add null checks for onVerify callback and opencode model selection
This commit is contained in:
@@ -71,10 +71,12 @@ import { createSetTrackingHandler } from './routes/set-tracking.js';
|
||||
import { createSyncHandler } from './routes/sync.js';
|
||||
import { createUpdatePRNumberHandler } from './routes/update-pr-number.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import type { FeatureLoader } from '../../services/feature-loader.js';
|
||||
|
||||
export function createWorktreeRoutes(
|
||||
events: EventEmitter,
|
||||
settingsService?: SettingsService
|
||||
settingsService?: SettingsService,
|
||||
featureLoader?: FeatureLoader
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
@@ -94,7 +96,11 @@ export function createWorktreeRoutes(
|
||||
validatePathParams('projectPath'),
|
||||
createCreateHandler(events, settingsService)
|
||||
);
|
||||
router.post('/delete', validatePathParams('projectPath', 'worktreePath'), createDeleteHandler());
|
||||
router.post(
|
||||
'/delete',
|
||||
validatePathParams('projectPath', 'worktreePath'),
|
||||
createDeleteHandler(events, featureLoader)
|
||||
);
|
||||
router.post('/create-pr', createCreatePRHandler());
|
||||
router.post('/pr-info', createPRInfoHandler());
|
||||
router.post(
|
||||
|
||||
@@ -10,11 +10,13 @@ import { isGitRepo } from '@automaker/git-utils';
|
||||
import { getErrorMessage, logError, isValidBranchName } from '../common.js';
|
||||
import { execGitCommand } from '../../../lib/git.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import type { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
const logger = createLogger('Worktree');
|
||||
|
||||
export function createDeleteHandler() {
|
||||
export function createDeleteHandler(events: EventEmitter, featureLoader?: FeatureLoader) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, worktreePath, deleteBranch } = req.body as {
|
||||
@@ -134,12 +136,65 @@ export function createDeleteHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
// Emit worktree:deleted event after successful deletion
|
||||
events.emit('worktree:deleted', {
|
||||
worktreePath,
|
||||
projectPath,
|
||||
branchName,
|
||||
branchDeleted,
|
||||
});
|
||||
|
||||
// Move features associated with the deleted branch to the main worktree
|
||||
// This prevents features from being orphaned when a worktree is deleted
|
||||
let featuresMovedToMain = 0;
|
||||
if (featureLoader && branchName) {
|
||||
try {
|
||||
const allFeatures = await featureLoader.getAll(projectPath);
|
||||
const affectedFeatures = allFeatures.filter((f) => f.branchName === branchName);
|
||||
for (const feature of affectedFeatures) {
|
||||
try {
|
||||
await featureLoader.update(projectPath, feature.id, {
|
||||
branchName: null,
|
||||
});
|
||||
featuresMovedToMain++;
|
||||
// Emit feature:migrated event for each successfully migrated feature
|
||||
events.emit('feature:migrated', {
|
||||
featureId: feature.id,
|
||||
status: 'migrated',
|
||||
fromBranch: branchName,
|
||||
toWorktreeId: null, // migrated to main worktree (no specific worktree)
|
||||
projectPath,
|
||||
});
|
||||
} catch (featureUpdateError) {
|
||||
// Non-fatal: log per-feature failure but continue migrating others
|
||||
logger.warn('Failed to move feature to main worktree after deletion', {
|
||||
error: getErrorMessage(featureUpdateError),
|
||||
featureId: feature.id,
|
||||
branchName,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (featuresMovedToMain > 0) {
|
||||
logger.info(
|
||||
`Moved ${featuresMovedToMain} feature(s) to main worktree after deleting worktree with branch: ${branchName}`
|
||||
);
|
||||
}
|
||||
} catch (featureError) {
|
||||
// Non-fatal: log but don't fail the deletion (getAll failed)
|
||||
logger.warn('Failed to load features for migration to main worktree after deletion', {
|
||||
error: getErrorMessage(featureError),
|
||||
branchName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
deleted: {
|
||||
worktreePath,
|
||||
branch: branchDeleted ? branchName : null,
|
||||
branchDeleted,
|
||||
featuresMovedToMain,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user