Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/core/src/worktree/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export type {
WorktreeResult,
BranchInfo,
CreateWorktreeOptions,
MergeWorktreeOptions,
MergeWorktreeResult,
WorktreeIncludeStatus,
Comment thread
mrubens marked this conversation as resolved.
WorktreeListResponse,
WorktreeDefaultsResponse,
Expand Down
131 changes: 1 addition & 130 deletions packages/core/src/worktree/worktree-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@ import { exec, execFile } from "child_process"
import * as path from "path"
import { promisify } from "util"

import type {
BranchInfo,
CreateWorktreeOptions,
MergeWorktreeOptions,
MergeWorktreeResult,
Worktree,
WorktreeResult,
} from "./types.js"
import type { BranchInfo, CreateWorktreeOptions, Worktree, WorktreeResult } from "./types.js"

const execAsync = promisify(exec)
const execFileAsync = promisify(execFile)
Expand Down Expand Up @@ -233,128 +226,6 @@ export class WorktreeService {
}
}

/**
* Merge a worktree branch into target branch
*/
async mergeWorktree(cwd: string, options: MergeWorktreeOptions): Promise<MergeWorktreeResult> {
const { worktreePath, targetBranch, deleteAfterMerge } = options

try {
// Get the worktree info to find its branch
const worktrees = await this.listWorktrees(cwd)
const worktree = worktrees.find((wt) => this.normalizePath(wt.path) === this.normalizePath(worktreePath))

if (!worktree) {
return {
success: false,
message: "Worktree not found",
hasConflicts: false,
conflictingFiles: [],
}
}

const sourceBranch = worktree.branch
if (!sourceBranch) {
return {
success: false,
message: "Worktree has detached HEAD - cannot merge",
hasConflicts: false,
conflictingFiles: [],
}
}

// Find the worktree that has the target branch checked out
const targetWorktree = worktrees.find((wt) => wt.branch === targetBranch)
const mergeCwd = targetWorktree ? targetWorktree.path : cwd

// Check for uncommitted changes in source worktree
try {
const { stdout: statusOutput } = await execAsync("git status --porcelain", { cwd: worktreePath })
if (statusOutput.trim()) {
return {
success: false,
message: "Source worktree has uncommitted changes. Please commit or stash them first.",
hasConflicts: false,
conflictingFiles: [],
sourceBranch,
targetBranch,
}
}
} catch {
// Continue if status check fails
}

// Ensure we're on the target branch
await execFileAsync("git", ["checkout", targetBranch], { cwd: mergeCwd })

// Attempt the merge
try {
await execFileAsync("git", ["merge", sourceBranch, "--no-edit"], { cwd: mergeCwd })

// Merge succeeded
if (deleteAfterMerge) {
await this.deleteWorktree(cwd, worktreePath, false)
}

return {
success: true,
message: `Successfully merged ${sourceBranch} into ${targetBranch}`,
hasConflicts: false,
conflictingFiles: [],
sourceBranch,
targetBranch,
}
} catch (mergeError) {
// Check for merge conflicts
try {
const { stdout: conflictOutput } = await execAsync("git diff --name-only --diff-filter=U", {
cwd: mergeCwd,
})
const conflictingFiles = conflictOutput.trim().split("\n").filter(Boolean)

// Abort the merge to leave repo in clean state
await execAsync("git merge --abort", { cwd: mergeCwd })

return {
success: false,
message: `Merge conflicts detected in ${conflictingFiles.length} file(s)`,
hasConflicts: true,
conflictingFiles,
sourceBranch,
targetBranch,
}
} catch {
// If we can't get conflicts, just report the error
const errorMessage = mergeError instanceof Error ? mergeError.message : String(mergeError)

// Try to abort any in-progress merge
try {
await execAsync("git merge --abort", { cwd: mergeCwd })
} catch {
// Ignore abort errors
}

return {
success: false,
message: `Merge failed: ${errorMessage}`,
hasConflicts: false,
conflictingFiles: [],
sourceBranch,
targetBranch,
}
}
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
return {
success: false,
message: `Merge failed: ${errorMessage}`,
hasConflicts: false,
conflictingFiles: [],
}
}
}

/**
* Checkout a branch in the current worktree
*/
Expand Down
30 changes: 0 additions & 30 deletions packages/types/src/worktree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,36 +65,6 @@ export interface CreateWorktreeOptions {
createNewBranch?: boolean
}

/**
* Options for merging a worktree branch
*/
export interface MergeWorktreeOptions {
/** Path to the worktree being merged */
worktreePath: string
/** Target branch to merge into */
targetBranch: string
/** If true, delete the worktree after successful merge */
deleteAfterMerge?: boolean
}

/**
* Result of a merge operation
*/
export interface MergeWorktreeResult {
/** Whether the merge succeeded */
success: boolean
/** Human-readable message describing the result */
message: string
/** Whether there are merge conflicts */
hasConflicts: boolean
/** List of files with conflicts */
conflictingFiles: string[]
/** Source branch that was merged */
sourceBranch?: string
/** Target branch that was merged into */
targetBranch?: string
}

/**
* Status of .worktreeinclude file
*/
Expand Down
33 changes: 0 additions & 33 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ import {
handleCheckBranchWorktreeInclude,
handleCreateWorktreeInclude,
handleCheckoutBranch,
handleMergeWorktree,
} from "./worktree"

export const webviewMessageHandler = async (
Expand Down Expand Up @@ -3557,38 +3556,6 @@ export const webviewMessageHandler = async (
break
}

case "mergeWorktree": {
try {
const result = await handleMergeWorktree(provider, {
worktreePath: message.worktreePath!,
targetBranch: message.worktreeTargetBranch!,
deleteAfterMerge: message.worktreeDeleteAfterMerge,
})

await provider.postMessageToWebview({
type: "mergeWorktreeResult",
success: result.success,
text: result.message,
hasConflicts: result.hasConflicts,
conflictingFiles: result.conflictingFiles,
sourceBranch: result.sourceBranch,
targetBranch: result.targetBranch,
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)

await provider.postMessageToWebview({
type: "mergeWorktreeResult",
success: false,
text: errorMessage,
hasConflicts: false,
conflictingFiles: [],
})
}

break
}

default: {
// console.log(`Unhandled message type: ${message.type}`)
//
Expand Down
13 changes: 0 additions & 13 deletions src/core/webview/worktree/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import * as os from "os"
import type {
WorktreeResult,
BranchInfo,
MergeWorktreeResult,
WorktreeIncludeStatus,
WorktreeListResponse,
WorktreeDefaultsResponse,
Expand Down Expand Up @@ -278,15 +277,3 @@ export async function handleCheckoutBranch(provider: ClineProvider, branch: stri
const cwd = provider.cwd
return worktreeService.checkoutBranch(cwd, branch)
}

export async function handleMergeWorktree(
provider: ClineProvider,
options: {
worktreePath: string
targetBranch: string
deleteAfterMerge?: boolean
},
): Promise<MergeWorktreeResult> {
const cwd = provider.cwd
return worktreeService.mergeWorktree(cwd, options)
}
1 change: 0 additions & 1 deletion src/core/webview/worktree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export {
handleCheckBranchWorktreeInclude,
handleCreateWorktreeInclude,
handleCheckoutBranch,
handleMergeWorktree,
} from "./handlers"

// Re-export types from @roo-code/types for convenience
Expand Down
Loading
Loading