merge.ts•2.45 kB
/**
 * @fileoverview Git merge operations
 * @module services/git/providers/cli/operations/branches/merge
 */
import type { RequestContext } from '@/utils/index.js';
import type {
  GitMergeOptions,
  GitMergeResult,
  GitOperationContext,
} from '../../../../types.js';
import {
  buildGitCommand,
  mapGitError,
  shouldSignCommits,
} from '../../utils/index.js';
/**
 * Execute git merge to integrate changes.
 */
export async function executeMerge(
  options: GitMergeOptions,
  context: GitOperationContext,
  execGit: (
    args: string[],
    cwd: string,
    ctx: RequestContext,
  ) => Promise<{ stdout: string; stderr: string }>,
): Promise<GitMergeResult> {
  try {
    const args = [options.branch];
    if (options.noFastForward) {
      args.push('--no-ff');
    }
    if (options.strategy) {
      args.push(`--strategy=${options.strategy}`);
    }
    if (options.squash) {
      args.push('--squash');
    }
    if (options.message) {
      args.push('-m', options.message);
    }
    // Add signing support - use explicit option or fall back to config default
    const shouldSign = options.sign ?? shouldSignCommits();
    if (shouldSign) {
      args.push('-S');
    }
    const cmd = buildGitCommand({ command: 'merge', args });
    const result = await execGit(
      cmd,
      context.workingDirectory,
      context.requestContext,
    );
    // Check for conflicts and fast-forward
    const hasConflicts =
      result.stdout.includes('CONFLICT') || result.stderr.includes('CONFLICT');
    const isFastForward = result.stdout.includes('Fast-forward');
    // Parse conflicted files
    const conflictedFiles = result.stdout
      .split('\n')
      .filter((line) => line.includes('CONFLICT'))
      .map((line) => {
        const match = line.match(/CONFLICT.*?in (.+)$/);
        return match?.[1] || '';
      })
      .filter((f) => f);
    // Parse merged files
    const mergedFiles = result.stdout
      .split('\n')
      .filter((line) => line.trim() && !line.includes('CONFLICT'))
      .map((line) => line.trim())
      .filter((f) => f);
    const mergeResult = {
      success: !hasConflicts,
      strategy: options.strategy || 'ort',
      fastForward: isFastForward,
      conflicts: hasConflicts,
      conflictedFiles,
      mergedFiles,
      message: options.message || result.stdout.trim(),
    };
    return mergeResult;
  } catch (error) {
    throw mapGitError(error, 'merge');
  }
}