stash.ts•4.06 kB
/**
 * @fileoverview CLI provider git stash operation
 * @module services/git/providers/cli/operations/stash/stash
 */
import type { RequestContext } from '@/utils/index.js';
import type {
  GitOperationContext,
  GitStashOptions,
  GitStashResult,
} from '../../../../types.js';
import { buildGitCommand, mapGitError } from '../../utils/index.js';
/**
 * Execute git stash operations.
 *
 * @param options - Stash options
 * @param context - Operation context
 * @param execGit - Function to execute git commands
 * @returns Stash result
 */
export async function executeStash(
  options: GitStashOptions,
  context: GitOperationContext,
  execGit: (
    args: string[],
    cwd: string,
    ctx: RequestContext,
  ) => Promise<{ stdout: string; stderr: string }>,
): Promise<GitStashResult> {
  try {
    const args: string[] = [options.mode];
    switch (options.mode) {
      case 'list': {
        const cmd = buildGitCommand({ command: 'stash', args });
        const result = await execGit(
          cmd,
          context.workingDirectory,
          context.requestContext,
        );
        // Parse stash list output
        const stashes = result.stdout
          .split('\n')
          .filter((line) => line.trim())
          .map((line, index) => {
            // Format: stash@{0}: WIP on branch: description
            const match = line.match(/^(stash@\{(\d+)\}): (.+)$/);
            if (match) {
              return {
                ref: match[1]!,
                index,
                branch: '',
                description: match[3]!,
                timestamp: 0,
              };
            }
            return {
              ref: `stash@{${index}}`,
              index,
              branch: '',
              description: line,
              timestamp: 0,
            };
          });
        const listResult = {
          mode: 'list' as const,
          stashes,
        };
        return listResult;
      }
      case 'push': {
        if (options.message) {
          args.push('-m', options.message);
        }
        if (options.includeUntracked) {
          args.push('--include-untracked');
        }
        if (options.keepIndex) {
          args.push('--keep-index');
        }
        const cmd = buildGitCommand({ command: 'stash', args });
        await execGit(cmd, context.workingDirectory, context.requestContext);
        const pushResult = {
          mode: 'push' as const,
          created: 'stash@{0}',
        };
        return pushResult;
      }
      case 'pop':
      case 'apply': {
        if (options.stashRef) {
          args.push(options.stashRef);
        }
        const cmd = buildGitCommand({ command: 'stash', args });
        const result = await execGit(
          cmd,
          context.workingDirectory,
          context.requestContext,
        );
        const hasConflicts =
          result.stdout.includes('CONFLICT') ||
          result.stderr.includes('CONFLICT');
        const applyResult = {
          mode: options.mode,
          applied: options.stashRef || 'stash@{0}',
          conflicts: hasConflicts,
        };
        return applyResult;
      }
      case 'drop': {
        if (!options.stashRef) {
          throw new Error('Stash reference is required for drop operation');
        }
        args.push(options.stashRef);
        const cmd = buildGitCommand({ command: 'stash', args });
        await execGit(cmd, context.workingDirectory, context.requestContext);
        const dropResult = {
          mode: 'drop' as const,
          dropped: options.stashRef,
        };
        return dropResult;
      }
      case 'clear': {
        const cmd = buildGitCommand({ command: 'stash', args });
        await execGit(cmd, context.workingDirectory, context.requestContext);
        const clearResult = {
          mode: 'clear' as const,
        };
        return clearResult;
      }
      default:
        throw new Error('Unknown stash operation mode');
    }
  } catch (error) {
    throw mapGitError(error, 'stash');
  }
}