UNPKG

@cyanheads/git-mcp-server

Version:

An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management,

112 lines 4.76 kB
/** * @fileoverview Defines the core logic, schemas, and types for the git_cherry-pick tool. * @module src/mcp-server/tools/gitCherryPick/logic */ import { execFile } from "child_process"; import { promisify } from "util"; import { z } from "zod"; import { logger, sanitization, } from "../../../utils/index.js"; import { McpError, BaseErrorCode } from "../../../types-global/errors.js"; import { config } from "../../../config/index.js"; import { getGitStatus, GitStatusOutputSchema } from "../gitStatus/logic.js"; const execFileAsync = promisify(execFile); // 1. DEFINE the Zod input schema. export const GitCherryPickInputSchema = z.object({ path: z .string() .default(".") .describe("Path to the Git repository. Defaults to the directory set via `git_set_working_dir` for the session; set 'git_set_working_dir' if not set."), commitRef: z .string() .min(1) .describe("The commit reference(s) to cherry-pick."), mainline: z .number() .int() .min(1) .optional() .describe("The parent number (1-based) for a merge commit."), strategy: z .enum(["recursive", "resolve", "ours", "theirs", "octopus", "subtree"]) .optional() .describe("The merge strategy to use."), noCommit: z .boolean() .default(false) .describe("Apply changes but do not create a commit."), signoff: z .boolean() .default(false) .describe("Add a 'Signed-off-by' line to the commit message."), }); // 2. DEFINE the Zod response schema. export const GitCherryPickOutputSchema = z.object({ success: z.boolean().describe("Indicates if the command was successful."), message: z.string().describe("A summary message of the result."), commitCreated: z.boolean().describe("Indicates if a new commit was created."), conflicts: z.boolean().describe("Indicates if conflicts occurred."), status: GitStatusOutputSchema.optional().describe("The status of the repository after the cherry-pick operation."), }); /** * 4. IMPLEMENT the core logic function. * @throws {McpError} If the logic encounters an unrecoverable issue. */ export async function gitCherryPickLogic(params, context) { const operation = "gitCherryPickLogic"; logger.debug(`Executing ${operation}`, { ...context, params }); const workingDir = context.getWorkingDirectory(); if (params.path === "." && !workingDir) { throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first."); } const targetPath = sanitization.sanitizePath(params.path === "." ? workingDir : params.path, { allowAbsolute: true }).sanitizedPath; const attemptCherryPick = async (withSigning) => { const args = ["-C", targetPath, "cherry-pick"]; if (withSigning) args.push("-S"); if (params.mainline) args.push("-m", String(params.mainline)); if (params.strategy) args.push(`-X${params.strategy}`); if (params.noCommit) args.push("--no-commit"); if (params.signoff) args.push("--signoff"); args.push(params.commitRef); logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation, }); return await execFileAsync("git", args); }; const createsCommit = !params.noCommit; const shouldSign = !!config.gitSignCommits && createsCommit; let stdout; let stderr; try { const result = await attemptCherryPick(shouldSign); stdout = result.stdout; stderr = result.stderr; } catch (error) { const execError = error; const isSigningError = (execError.stderr || "").includes("gpg failed to sign"); if (shouldSign && isSigningError) { logger.warning("Cherry-pick with signing failed. Retrying automatically without signature.", { ...context, operation }); const result = await attemptCherryPick(false); stdout = result.stdout; stderr = result.stderr; } else { throw error; } } const output = stdout + stderr; const conflicts = /conflict/i.test(output); const commitCreated = createsCommit && !conflicts; const message = conflicts ? `Cherry-pick resulted in conflicts for commit(s) '${params.commitRef}'. Manual resolution required.` : `Successfully cherry-picked commit(s) '${params.commitRef}'.`; const status = await getGitStatus({ path: targetPath }, context); return { success: true, message, commitCreated, conflicts, status }; } //# sourceMappingURL=logic.js.map