-
Notifications
You must be signed in to change notification settings - Fork 578
Fix orphaned features when deleting worktrees #820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1b5a6c0
a6d7e6a
a77202f
dcb110d
86e282f
fb7e4fe
749f648
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
| ); | ||
|
Comment on lines
+99
to
+103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forward The delete flow now mutates feature state, but this route wiring only passes 💡 Suggested wiring update router.post(
'/delete',
validatePathParams('projectPath', 'worktreePath'),
- createDeleteHandler(featureLoader)
+ createDeleteHandler(events, featureLoader)
);// Companion update in apps/server/src/routes/worktree/routes/delete.ts
export function createDeleteHandler(events: EventEmitter, featureLoader?: FeatureLoader) { ... }As per coding guidelines 🤖 Prompt for AI Agents |
||
| router.post('/create-pr', createCreatePRHandler()); | ||
| router.post('/pr-info', createPRInfoHandler()); | ||
| router.post( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a shared This new relative service import violates the repository import rule for TS/JS files. As per coding guidelines 🤖 Prompt for AI Agents |
||
| 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> => { | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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, | ||
| }); | ||
| } | ||
| } | ||
|
Comment on lines
+154
to
+176
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The feature updates are currently performed sequentially in a await Promise.all(
affectedFeatures.map((feature) =>
featureLoader.update(projectPath, feature.id, {
branchName: null,
})
)
); |
||
| 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, | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| }); | ||
| } catch (error) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a shared
@automaker/*import forFeatureLoader.This newly added relative import conflicts with the TS/JS import rule.
As per coding guidelines
**/*.{ts,tsx,js,jsx}: Always import from shared packages (@automaker/*), never from old paths like '../services/feature-loader' or '../lib/logger'.🤖 Prompt for AI Agents