MCPcopy Create free account
hub / github.com/agent-tower/core / merge

Method merge

packages/server/src/git/worktree.manager.ts:396–489  ·  view source on GitHub ↗

* Squash-merge a worktree branch into the target branch. * * Performs the following steps: * 1. Verify the worktree is clean * 2. Check branch divergence — fail if base branch has advanced * 3. Checkout target branch in the main repo * 4. Execute `git merge --squash --no-commit <ta

(
    worktreePath: string,
    targetBranch: string,
    options?: { commitMessage?: string }
  )

Source from the content-addressed store, hash-verified

394 * 6. Remove the worktree and delete the task branch
395 */
396 async merge(
397 worktreePath: string,
398 targetBranch: string,
399 options?: { commitMessage?: string }
400 ): Promise<{ sha: string; taskBranch: string }> {
401 const mergeStart = performance.now();
402 const step = (label: string, start: number) =>
403 console.log(`[WorktreeManager.merge] ${label}: ${(performance.now() - start).toFixed(0)}ms`);
404
405 // Determine the current branch of the worktree
406 let t = performance.now();
407 const currentBranchRaw = await execGit(worktreePath, ['rev-parse', '--abbrev-ref', 'HEAD']);
408 const taskBranch = currentBranchRaw.trim();
409 step('rev-parse branch', t);
410
411 // 1. Check worktree is clean
412 t = performance.now();
413 const clean = await this.isWorktreeClean(worktreePath);
414 step('isWorktreeClean', t);
415 if (!clean) {
416 throw new WorktreeDirtyError(worktreePath);
417 }
418
419 // 2. Check branch divergence
420 t = performance.now();
421 const status = await this.getBranchStatus(taskBranch, targetBranch);
422 step('getBranchStatus', t);
423 if (status.behind > 0) {
424 throw new BranchesDivergedError(taskBranch, targetBranch, status.ahead, status.behind);
425 }
426
427 // 3. Checkout target branch in main repo
428 t = performance.now();
429 await execGit(this.repoPath, ['checkout', targetBranch]);
430 step('checkout target', t);
431
432 // 4. Squash merge (no commit yet)
433 t = performance.now();
434 try {
435 await execGit(this.repoPath, [
436 'merge',
437 '--squash',
438 '--no-commit',
439 taskBranch,
440 ]);
441 } catch (err) {
442 if (err instanceof GitError) {
443 // Check for merge conflicts
444 const conflictedFiles = await this.getConflictedFiles();
445 if (conflictedFiles.length > 0) {
446 // Abort the merge to leave the repo clean
447 const mergeAborted = await execGit(this.repoPath, ['merge', '--abort']).then(() => true).catch(() => {
448 // merge --abort may fail if no merge in progress, ignore
449 return false;
450 });
451 throw new MergeConflictError(conflictedFiles, ConflictOp.MERGE, {
452 mergeAborted,
453 mergeStrategy: 'squash',

Callers 1

Calls 5

isWorktreeCleanMethod · 0.95
getBranchStatusMethod · 0.95
getConflictedFilesMethod · 0.95
trimMethod · 0.80
execGitFunction · 0.70

Tested by

no test coverage detected