push.ts•2.22 kB
/**
 * @fileoverview CLI provider git push operation
 * @module services/git/providers/cli/operations/remotes/push
 */
import type { RequestContext } from '@/utils/index.js';
import type {
  GitOperationContext,
  GitPushOptions,
  GitPushResult,
} from '../../../../types.js';
import { buildGitCommand, mapGitError } from '../../utils/index.js';
/**
 * Execute git push to upload local changes.
 */
export async function executePush(
  options: GitPushOptions,
  context: GitOperationContext,
  execGit: (
    args: string[],
    cwd: string,
    ctx: RequestContext,
  ) => Promise<{ stdout: string; stderr: string }>,
): Promise<GitPushResult> {
  try {
    const args: string[] = [];
    const remote = options.remote || 'origin';
    args.push(remote);
    if (options.branch) {
      args.push(options.branch);
    }
    if (options.force) {
      args.push('--force');
    } else if (options.forceWithLease) {
      args.push('--force-with-lease');
    }
    if (options.setUpstream) {
      args.push('--set-upstream');
    }
    if (options.tags) {
      args.push('--tags');
    }
    if (options.dryRun) {
      args.push('--dry-run');
    }
    const cmd = buildGitCommand({ command: 'push', args });
    const gitOutput = await execGit(
      cmd,
      context.workingDirectory,
      context.requestContext,
    );
    // Parse pushed and rejected refs
    const pushedRefs: string[] = [];
    const rejectedRefs: string[] = [];
    const lines = gitOutput.stderr.split('\n'); // git push outputs to stderr
    for (const line of lines) {
      if (line.includes('->')) {
        const match = line.match(/\*\s+\[new branch\]\s+(\S+)/);
        if (match) {
          pushedRefs.push(match[1]!);
        }
      }
      if (line.includes('rejected')) {
        const match = line.match(/!\s+\[rejected\]\s+(\S+)/);
        if (match) {
          rejectedRefs.push(match[1]!);
        }
      }
    }
    const result = {
      success: rejectedRefs.length === 0,
      remote,
      branch: options.branch || 'HEAD',
      upstreamSet: options.setUpstream || false,
      pushedRefs,
      rejectedRefs,
    };
    return result;
  } catch (error) {
    throw mapGitError(error, 'push');
  }
}