diff --git a/index.ts b/index.ts index 502c188..c4d2b35 100644 --- a/index.ts +++ b/index.ts @@ -53,7 +53,7 @@ import { CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, - GitLabMergeRequestDiffSchema, + GitLabDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, @@ -126,6 +126,9 @@ import { GetRepositoryTreeSchema, type GitLabTreeItem, type GetRepositoryTreeOptions, + GitLabCompareResult, + GitLabCompareResultSchema, + GetBranchDiffsSchema, } from "./schemas.js"; /** @@ -261,6 +264,12 @@ const allTools = [ "Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)", inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema), }, + { + name: "get_branch_diffs", + description: + "Get the changes/diffs between two branches or commits in a GitLab project", + inputSchema: zodToJsonSchema(GetBranchDiffsSchema), + }, { name: "update_merge_request", description: @@ -436,6 +445,7 @@ const readOnlyTools = [ "get_file_contents", "get_merge_request", "get_merge_request_diffs", + "get_branch_diffs", "mr_discussions", "list_issues", "get_issue", @@ -1552,7 +1562,50 @@ async function getMergeRequestDiffs( await handleGitLabError(response); const data = (await response.json()) as { changes: unknown }; - return z.array(GitLabMergeRequestDiffSchema).parse(data.changes); + return z.array(GitLabDiffSchema).parse(data.changes); +} + +/** + * Get branch comparison diffs + * + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {string} from - The branch name or commit SHA to compare from + * @param {string} to - The branch name or commit SHA to compare to + * @param {boolean} [straight] - Comparison method: false for '...' (default), true for '--' + * @returns {Promise} Branch comparison results + */ +async function getBranchDiffs( + projectId: string, + from: string, + to: string, + straight?: boolean +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/compare` + ); + + url.searchParams.append("from", from); + url.searchParams.append("to", to); + + if (straight !== undefined) { + url.searchParams.append("straight", straight.toString()); + } + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + }); + + if (!response.ok) { + const errorBody = await response.text(); + throw new Error( + `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}` + ); + } + + const data = await response.json(); + return GitLabCompareResultSchema.parse(data); } /** @@ -2414,6 +2467,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } + case "get_branch_diffs": { + const args = GetBranchDiffsSchema.parse(request.params.arguments); + const diffs = await getBranchDiffs( + args.project_id, + args.from, + args.to, + args.straight + ); + return { + content: [{ type: "text", text: JSON.stringify(diffs, null, 2) }], + }; + } + case "search_repositories": { const args = SearchRepositoriesSchema.parse(request.params.arguments); const results = await searchProjects( diff --git a/schemas.ts b/schemas.ts index 4198432..41d1270 100644 --- a/schemas.ts +++ b/schemas.ts @@ -271,9 +271,15 @@ export const CreateMergeRequestOptionsSchema = z.object({ draft: z.boolean().optional(), }); -export const CreateBranchOptionsSchema = z.object({ - name: z.string(), // Changed from ref to match GitLab API - ref: z.string(), // The source branch/commit for the new branch +export const GitLabDiffSchema = z.object({ + old_path: z.string(), + new_path: z.string(), + a_mode: z.string(), + b_mode: z.string(), + diff: z.string(), + new_file: z.boolean(), + renamed_file: z.boolean(), + deleted_file: z.boolean(), }); // Response schemas for operations @@ -291,6 +297,27 @@ export const GitLabSearchResponseSchema = z.object({ items: z.array(GitLabRepositorySchema), }); +// create branch schemas +export const CreateBranchOptionsSchema = z.object({ + name: z.string(), // Changed from ref to match GitLab API + ref: z.string(), // The source branch/commit for the new branch +}); + +export const GitLabCompareResultSchema = z.object({ + commit: z.object({ + id: z.string().optional(), + short_id: z.string().optional(), + title: z.string().optional(), + author_name: z.string().optional(), + author_email: z.string().optional(), + created_at: z.string().optional(), + }).optional(), + commits: z.array(GitLabCommitSchema), + diffs: z.array(GitLabDiffSchema), + compare_timeout: z.boolean().optional(), + compare_same_ref: z.boolean().optional(), +}); + // Issue related schemas export const GitLabLabelSchema = z.object({ id: z.number(), @@ -596,20 +623,16 @@ export const ForkRepositorySchema = ProjectParamsSchema.extend({ namespace: z.string().optional().describe("Namespace to fork to (full path)"), }); +// Branch related schemas export const CreateBranchSchema = ProjectParamsSchema.extend({ branch: z.string().describe("Name for the new branch"), ref: z.string().optional().describe("Source branch/commit for new branch"), }); -export const GitLabMergeRequestDiffSchema = z.object({ - old_path: z.string(), - new_path: z.string(), - a_mode: z.string(), - b_mode: z.string(), - diff: z.string(), - new_file: z.boolean(), - renamed_file: z.boolean(), - deleted_file: z.boolean(), +export const GetBranchDiffsSchema = ProjectParamsSchema.extend({ + from: z.string().describe("The base branch or commit SHA to compare from"), + to: z.string().describe("The target branch or commit SHA to compare to"), + straight: z.boolean().optional().describe("Comparison method: false for '...' (default), true for '--'"), }); export const GetMergeRequestSchema = ProjectParamsSchema.extend({ @@ -1082,6 +1105,7 @@ export type GitLabDirectoryContent = z.infer< export type GitLabContent = z.infer; export type FileOperation = z.infer; export type GitLabTree = z.infer; +export type GitLabCompareResult = z.infer; export type GitLabCommit = z.infer; export type GitLabReference = z.infer; export type CreateRepositoryOptions = z.infer< @@ -1097,7 +1121,7 @@ export type GitLabCreateUpdateFileResponse = z.infer< >; export type GitLabSearchResponse = z.infer; export type GitLabMergeRequestDiff = z.infer< - typeof GitLabMergeRequestDiffSchema + typeof GitLabDiffSchema >; export type CreateNoteOptions = z.infer; export type GitLabIssueLink = z.infer;