From 672953ff40ce6a0a483dabcfd28f92c061fe7282 Mon Sep 17 00:00:00 2001 From: Iwaki Takuma <39580206+iwakitakuma33@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:56:44 +0900 Subject: [PATCH] FEAT: mr discussion with code diff (#93) issue: #90 Made it possible to select multiple lines of diff when creating MR discussions. The API is difficult to use, so there is a possibility that AI will not be able to create appropriate payloads. --- README.md | 109 +++++++++++++------------- index.ts | 225 +++++++++++++++++++++++++++++++++++++++++++++++++++++ schemas.ts | 106 +++++++++++++++++++------ 3 files changed, 364 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 7ead073..85deecb 100644 --- a/README.md +++ b/README.md @@ -133,58 +133,59 @@ $ sh scripts/image_push.sh docker_user_name 9. `create_branch` - Create a new branch in a GitLab project 10. `get_merge_request` - Get details of a merge request (Either mergeRequestIid or branchName must be provided) 11. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided) -12. `get_branch_diffs` - Get the changes/diffs between two branches or commits in a GitLab project -13. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided) -14. `create_note` - Create a new note (comment) to an issue or merge request -15. `create_merge_request_thread` - Create a new thread on a merge request -16. `mr_discussions` - List discussion items for a merge request -17. `update_merge_request_note` - Modify an existing merge request thread note -18. `create_merge_request_note` - Add a new note to an existing merge request thread -19. `update_issue_note` - Modify an existing issue thread note -20. `create_issue_note` - Add a new note to an existing issue thread -21. `list_issues` - List issues in a GitLab project with filtering options -22. `get_issue` - Get details of a specific issue in a GitLab project -23. `update_issue` - Update an issue in a GitLab project -24. `delete_issue` - Delete an issue from a GitLab project -25. `list_issue_links` - List all issue links for a specific issue -26. `list_issue_discussions` - List discussions for an issue in a GitLab project -27. `get_issue_link` - Get a specific issue link -28. `create_issue_link` - Create an issue link between two issues -29. `delete_issue_link` - Delete an issue link -30. `list_namespaces` - List all namespaces available to the current user -31. `get_namespace` - Get details of a namespace by ID or path -32. `verify_namespace` - Verify if a namespace path exists -33. `get_project` - Get details of a specific project -34. `list_projects` - List projects accessible by the current user -35. `list_labels` - List labels for a project -36. `get_label` - Get a single label from a project -37. `create_label` - Create a new label in a project -38. `update_label` - Update an existing label in a project -39. `delete_label` - Delete a label from a project -40. `list_group_projects` - List projects in a GitLab group with filtering options -41. `list_wiki_pages` - List wiki pages in a GitLab project -42. `get_wiki_page` - Get details of a specific wiki page -43. `create_wiki_page` - Create a new wiki page in a GitLab project -44. `update_wiki_page` - Update an existing wiki page in a GitLab project -45. `delete_wiki_page` - Delete a wiki page from a GitLab project -46. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) -47. `list_pipelines` - List pipelines in a GitLab project with filtering options -48. `get_pipeline` - Get details of a specific pipeline in a GitLab project -49. `list_pipeline_jobs` - List all jobs in a specific pipeline -50. `get_pipeline_job` - Get details of a GitLab pipeline job number -51. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job number -52. `create_pipeline` - Create a new pipeline for a branch or tag -53. `retry_pipeline` - Retry a failed or canceled pipeline -54. `cancel_pipeline` - Cancel a running pipeline -55. `list_merge_requests` - List merge requests in a GitLab project with filtering options -56. `list_milestones` - List milestones in a GitLab project with filtering options -57. `get_milestone` - Get details of a specific milestone -58. `create_milestone` - Create a new milestone in a GitLab project -59. `edit_milestone` - Edit an existing milestone in a GitLab project -60. `delete_milestone` - Delete a milestone from a GitLab project -61. `get_milestone_issue` - Get issues associated with a specific milestone -62. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone -63. `promote_milestone` - Promote a milestone to the next stage -64. `get_milestone_burndown_events` - Get burndown events for a specific milestone -65. `get_users` - Get GitLab user details by usernames +12. `list_merge_request_diffs` - List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided) +13. `get_branch_diffs` - Get the changes/diffs between two branches or commits in a GitLab project +14. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided) +15. `create_note` - Create a new note (comment) to an issue or merge request +16. `create_merge_request_thread` - Create a new thread on a merge request +17. `mr_discussions` - List discussion items for a merge request +18. `update_merge_request_note` - Modify an existing merge request thread note +19. `create_merge_request_note` - Add a new note to an existing merge request thread +20. `update_issue_note` - Modify an existing issue thread note +21. `create_issue_note` - Add a new note to an existing issue thread +22. `list_issues` - List issues in a GitLab project with filtering options +23. `get_issue` - Get details of a specific issue in a GitLab project +24. `update_issue` - Update an issue in a GitLab project +25. `delete_issue` - Delete an issue from a GitLab project +26. `list_issue_links` - List all issue links for a specific issue +27. `list_issue_discussions` - List discussions for an issue in a GitLab project +28. `get_issue_link` - Get a specific issue link +29. `create_issue_link` - Create an issue link between two issues +30. `delete_issue_link` - Delete an issue link +31. `list_namespaces` - List all namespaces available to the current user +32. `get_namespace` - Get details of a namespace by ID or path +33. `verify_namespace` - Verify if a namespace path exists +34. `get_project` - Get details of a specific project +35. `list_projects` - List projects accessible by the current user +36. `list_labels` - List labels for a project +37. `get_label` - Get a single label from a project +38. `create_label` - Create a new label in a project +39. `update_label` - Update an existing label in a project +40. `delete_label` - Delete a label from a project +41. `list_group_projects` - List projects in a GitLab group with filtering options +42. `list_wiki_pages` - List wiki pages in a GitLab project +43. `get_wiki_page` - Get details of a specific wiki page +44. `create_wiki_page` - Create a new wiki page in a GitLab project +45. `update_wiki_page` - Update an existing wiki page in a GitLab project +46. `delete_wiki_page` - Delete a wiki page from a GitLab project +47. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) +48. `list_pipelines` - List pipelines in a GitLab project with filtering options +49. `get_pipeline` - Get details of a specific pipeline in a GitLab project +50. `list_pipeline_jobs` - List all jobs in a specific pipeline +51. `get_pipeline_job` - Get details of a GitLab pipeline job number +52. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job number +53. `create_pipeline` - Create a new pipeline for a branch or tag +54. `retry_pipeline` - Retry a failed or canceled pipeline +55. `cancel_pipeline` - Cancel a running pipeline +56. `list_merge_requests` - List merge requests in a GitLab project with filtering options +57. `list_milestones` - List milestones in a GitLab project with filtering options +58. `get_milestone` - Get details of a specific milestone +59. `create_milestone` - Create a new milestone in a GitLab project +60. `edit_milestone` - Edit an existing milestone in a GitLab project +61. `delete_milestone` - Delete a milestone from a GitLab project +62. `get_milestone_issue` - Get issues associated with a specific milestone +63. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone +64. `promote_milestone` - Promote a milestone to the next stage +65. `get_milestone_burndown_events` - Get burndown events for a specific milestone +66. `get_users` - Get GitLab user details by usernames diff --git a/index.ts b/index.ts index b92f3b8..b80cac5 100644 --- a/index.ts +++ b/index.ts @@ -174,6 +174,13 @@ import { GitLabCompareResultSchema, GetBranchDiffsSchema, ListWikiPagesOptions, + ListCommitsSchema, + GetCommitSchema, + GetCommitDiffSchema, + type ListCommitsOptions, + type GetCommitOptions, + type GetCommitDiffOptions, + ListMergeRequestDiffsSchema, } from "./schemas.js"; /** @@ -411,6 +418,12 @@ const allTools = [ "Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)", inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema), }, + { + name: "list_merge_request_diffs", + description: + "List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided)", + inputSchema: zodToJsonSchema(ListMergeRequestDiffsSchema), + }, { name: "get_branch_diffs", description: "Get the changes/diffs between two branches or commits in a GitLab project", @@ -681,6 +694,21 @@ const allTools = [ description: "Get GitLab user details by usernames", inputSchema: zodToJsonSchema(GetUsersSchema), }, + { + name: "list_commits", + description: "List repository commits with filtering options", + inputSchema: zodToJsonSchema(ListCommitsSchema), + }, + { + name: "get_commit", + description: "Get details of a specific commit", + inputSchema: zodToJsonSchema(GetCommitSchema), + }, + { + name: "get_commit_diff", + description: "Get changes/diffs of a specific commit", + inputSchema: zodToJsonSchema(GetCommitDiffSchema), + }, ]; // Define which tools are read-only @@ -719,6 +747,9 @@ const readOnlyTools = [ "list_wiki_pages", "get_wiki_page", "get_users", + "list_commits", + "get_commit", + "get_commit_diff", ]; // Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI @@ -1922,6 +1953,60 @@ async function getMergeRequestDiffs( return z.array(GitLabDiffSchema).parse(data.changes); } +/** + * Get merge request changes with detailed information including commits, diff_refs, and more + * 마지막으로 추가된 상세한 MR 변경사항 조회 함수 (Detailed merge request changes retrieval function) + * + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {number} mergeRequestIid - The internal ID of the merge request (Either mergeRequestIid or branchName must be provided) + * @param {string} [branchName] - The name of the branch to search for merge request by branch name (Either mergeRequestIid or branchName must be provided) + * @param {boolean} [unidiff] - Return diff in unidiff format + * @returns {Promise} The complete merge request changes response + */ +async function listMergeRequestDiffs( + projectId: string, + mergeRequestIid?: number, + branchName?: string, + page?: number, + perPage?: number, + unidiff?: boolean +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + if (!mergeRequestIid && !branchName) { + throw new Error("Either mergeRequestIid or branchName must be provided"); + } + + if (branchName && !mergeRequestIid) { + const mergeRequest = await getMergeRequest(projectId, undefined, branchName); + mergeRequestIid = mergeRequest.iid; + } + + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent( + projectId + )}/merge_requests/${mergeRequestIid}/diffs` + ); + + if (page) { + url.searchParams.append("page", page.toString()); + } + + if (perPage) { + url.searchParams.append("per_page", perPage.toString()); + } + + if (unidiff) { + url.searchParams.append("unidiff", "true"); + } + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + }); + + await handleGitLabError(response); + return await response.json(); // Return full response including commits, diff_refs, changes, etc. +} + /** * Get branch comparison diffs * @@ -3154,6 +3239,107 @@ async function getUsers(usernames: string[]): Promise { return GitLabUsersResponseSchema.parse(users); } +/** + * List repository commits + * 저장소 커밋 목록 조회 + * + * @param {string} projectId - Project ID or URL-encoded path + * @param {ListCommitsOptions} options - List commits options + * @returns {Promise} List of commits + */ +async function listCommits( + projectId: string, + options: Omit = {} +): Promise { + projectId = decodeURIComponent(projectId); + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits` + ); + + // Add query parameters + if (options.ref_name) url.searchParams.append("ref_name", options.ref_name); + if (options.since) url.searchParams.append("since", options.since); + if (options.until) url.searchParams.append("until", options.until); + if (options.path) url.searchParams.append("path", options.path); + if (options.author) url.searchParams.append("author", options.author); + if (options.all) url.searchParams.append("all", options.all.toString()); + if (options.with_stats) url.searchParams.append("with_stats", options.with_stats.toString()); + if (options.first_parent) url.searchParams.append("first_parent", options.first_parent.toString()); + if (options.order) url.searchParams.append("order", options.order); + if (options.trailers) url.searchParams.append("trailers", options.trailers.toString()); + if (options.page) url.searchParams.append("page", options.page.toString()); + if (options.per_page) url.searchParams.append("per_page", options.per_page.toString()); + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + }); + + await handleGitLabError(response); + + const data = await response.json(); + return z.array(GitLabCommitSchema).parse(data); +} + +/** + * Get a single commit + * 단일 커밋 정보 조회 + * + * @param {string} projectId - Project ID or URL-encoded path + * @param {string} sha - The commit hash or name of a repository branch or tag + * @param {boolean} [stats] - Include commit stats + * @returns {Promise} The commit details + */ +async function getCommit( + projectId: string, + sha: string, + stats?: boolean +): Promise { + projectId = decodeURIComponent(projectId); + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits/${encodeURIComponent(sha)}` + ); + + if (stats) { + url.searchParams.append("stats", "true"); + } + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + }); + + await handleGitLabError(response); + + const data = await response.json(); + return GitLabCommitSchema.parse(data); +} + +/** + * Get commit diff + * 커밋 변경사항 조회 + * + * @param {string} projectId - Project ID or URL-encoded path + * @param {string} sha - The commit hash or name of a repository branch or tag + * @returns {Promise} The commit diffs + */ +async function getCommitDiff( + projectId: string, + sha: string +): Promise { + projectId = decodeURIComponent(projectId); + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits/${encodeURIComponent(sha)}/diff` + ); + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + }); + + await handleGitLabError(response); + + const data = await response.json(); + return z.array(GitLabDiffSchema).parse(data); +} + server.setRequestHandler(ListToolsRequestSchema, async () => { // Apply read-only filter first const tools0 = GITLAB_READ_ONLY_MODE @@ -3420,6 +3606,21 @@ server.setRequestHandler(CallToolRequestSchema, async request => { }; } + case "list_merge_request_diffs": { + const args = ListMergeRequestDiffsSchema.parse(request.params.arguments); + const changes = await listMergeRequestDiffs( + args.project_id, + args.merge_request_iid, + args.source_branch, + args.page, + args.per_page, + args.unidiff + ); + return { + content: [{ type: "text", text: JSON.stringify(changes, null, 2) }], + }; + } + case "update_merge_request": { const args = UpdateMergeRequestSchema.parse(request.params.arguments); const { project_id, merge_request_iid, source_branch, ...options } = args; @@ -4053,6 +4254,30 @@ server.setRequestHandler(CallToolRequestSchema, async request => { }; } + case "list_commits": { + const args = ListCommitsSchema.parse(request.params.arguments); + const commits = await listCommits(args.project_id, args); + return { + content: [{ type: "text", text: JSON.stringify(commits, null, 2) }], + }; + } + + case "get_commit": { + const args = GetCommitSchema.parse(request.params.arguments); + const commit = await getCommit(args.project_id, args.sha, args.stats); + return { + content: [{ type: "text", text: JSON.stringify(commit, null, 2) }], + }; + } + + case "get_commit_diff": { + const args = GetCommitDiffSchema.parse(request.params.arguments); + const diff = await getCommitDiff(args.project_id, args.sha); + return { + content: [{ type: "text", text: JSON.stringify(diff, null, 2) }], + }; + } + default: throw new Error(`Unknown tool: ${request.params.name}`); } diff --git a/schemas.ts b/schemas.ts index 9b9ac96..db0a1d3 100644 --- a/schemas.ts +++ b/schemas.ts @@ -409,8 +409,17 @@ export const GitLabCommitSchema = z.object({ committer_name: z.string(), committer_email: z.string(), committed_date: z.string(), + created_at: z.string().optional(), // Add created_at field + message: z.string().optional(), // Add full message field web_url: z.string(), // Changed from html_url to match GitLab API parent_ids: z.array(z.string()), // Changed from parents to match GitLab API + stats: z.object({ + additions: z.number().optional().nullable(), + deletions: z.number().optional().nullable(), + total: z.number().optional().nullable(), + }).optional(), // Only present when with_stats=true + trailers: z.record(z.string()).optional().default({}), // Git trailers, may be empty object + extended_trailers: z.record(z.array(z.string())).optional().default({}), // Extended trailers, may be empty object }); // Reference schema @@ -650,6 +659,21 @@ export const GitLabMergeRequestSchema = z.object({ labels: z.array(z.string()).optional(), }); +export const LineRangeSchema = z.object({ + start: z.object({ + line_code: z.string().nullable().optional().describe("CRITICAL: Line identifier in format '{file_path_sha1_hash}_{old_line_number}_{new_line_number}'. USUALLY REQUIRED for GitLab diff comments despite being optional in schema. Example: 'a1b2c3d4e5f6_10_15'. Get this from GitLab diff API response, never fabricate."), + type: z.enum(["new", "old"]).nullable().optional().describe("Line type: 'old' = deleted/original line, 'new' = added/modified line, null = unchanged context. MUST match the line_code format and old_line/new_line values."), + old_line: z.number().nullable().optional().describe("Line number in original file (before changes). REQUIRED when type='old', NULL when type='new' (for purely added lines), can be present for context lines."), + new_line: z.number().nullable().optional().describe("Line number in modified file (after changes). REQUIRED when type='new', NULL when type='old' (for purely deleted lines), can be present for context lines."), + }).describe("Start line position for multiline comment range. MUST specify either old_line OR new_line (or both for context), never neither."), + end: z.object({ + line_code: z.string().nullable().optional().describe("CRITICAL: Line identifier in format '{file_path_sha1_hash}_{old_line_number}_{new_line_number}'. USUALLY REQUIRED for GitLab diff comments despite being optional in schema. Example: 'a1b2c3d4e5f6_12_17'. Must be from same file as start.line_code."), + type: z.enum(["new", "old"]).nullable().optional().describe("Line type: 'old' = deleted/original line, 'new' = added/modified line, null = unchanged context. SHOULD MATCH start.type for consistent ranges (don't mix old/new types)."), + old_line: z.number().nullable().optional().describe("Line number in original file (before changes). REQUIRED when type='old', NULL when type='new' (for purely added lines), can be present for context lines. MUST be >= start.old_line if both specified."), + new_line: z.number().nullable().optional().describe("Line number in modified file (after changes). REQUIRED when type='new', NULL when type='old' (for purely deleted lines), can be present for context lines. MUST be >= start.new_line if both specified."), + }).describe("End line position for multiline comment range. MUST specify either old_line OR new_line (or both for context), never neither. Range must be valid (end >= start)."), +}).describe("Line range for multiline comments on GitLab merge request diffs. VALIDATION RULES: 1) line_code is critical for GitLab API success, 2) start/end must have consistent types, 3) line numbers must form valid range, 4) get line_code from GitLab diff API, never generate manually."); + // Discussion related schemas export const GitLabDiscussionNoteSchema = z.object({ id: z.number(), @@ -674,24 +698,24 @@ export const GitLabDiscussionNoteSchema = z.object({ base_sha: z.string(), start_sha: z.string(), head_sha: z.string(), - old_path: z.string(), - new_path: z.string(), + old_path: z.string().optional().describe("File path before change"), + new_path: z.string().optional().describe("File path after change"), position_type: z.enum(["text", "image", "file"]), - old_line: z.number().nullish(), // This is missing for image diffs - new_line: z.number().nullish(), // This is missing for image diffs + new_line: z.number().nullable().optional().describe("Line number in the modified file (after changes). Used for added lines and context lines. Null for deleted lines."), + old_line: z.number().nullable().optional().describe("Line number in the original file (before changes). Used for deleted lines and context lines. Null for newly added lines."), line_range: z .object({ start: z.object({ - line_code: z.string(), + line_code: z.string().nullable().optional().describe("Line identifier in format: '{file_path_sha1_hash}_{old_line_number}_{new_line_number}'. Used to uniquely identify a specific line in the diff."), type: z.enum(["new", "old", "expanded"]), - old_line: z.number().nullish(), // This is missing for image diffs - new_line: z.number().nullish(), // This is missing for image diffs + old_line: z.number().nullable().optional().describe("Line number in the original file (before changes). Null for newly added lines or unchanged context lines."), + new_line: z.number().nullable().optional().describe("Line number in the modified file (after changes). Null for deleted lines or unchanged context lines."), }), end: z.object({ - line_code: z.string(), + line_code: z.string().nullable().optional().describe("Line identifier in format: '{file_path_sha1_hash}_{old_line_number}_{new_line_number}'. Used to uniquely identify a specific line in the diff."), type: z.enum(["new", "old", "expanded"]), - old_line: z.number().nullish(), // This is missing for image diffs - new_line: z.number().nullish(), // This is missing for image diffs + old_line: z.number().nullable().optional().describe("Line number in the original file (before changes). Null for newly added lines or unchanged context lines."), + new_line: z.number().nullable().optional().describe("Line number in the modified file (after changes). Null for deleted lines or unchanged context lines."), }), }) .nullable() @@ -907,6 +931,12 @@ export const GetMergeRequestDiffsSchema = GetMergeRequestSchema.extend({ view: z.enum(["inline", "parallel"]).optional().describe("Diff view type"), }); +export const ListMergeRequestDiffsSchema = GetMergeRequestSchema.extend({ + page: z.number().optional().describe("Page number for pagination (default: 1)"), + per_page: z.number().optional().describe("Number of items per page (max: 100, default: 20)"), + unidiff: z.boolean().optional().describe("Present diffs in the unified diff format. Default is false. Introduced in GitLab 16.5."), +}); + export const CreateNoteSchema = z.object({ project_id: z.string().describe("Project ID or namespace/project_path"), noteable_type: z @@ -1239,18 +1269,19 @@ export const GitLabWikiPageSchema = z.object({ // Merge Request Thread position schema - used for diff notes export const MergeRequestThreadPositionSchema = z.object({ - base_sha: z.string().describe("Base commit SHA in the source branch"), - head_sha: z.string().describe("SHA referencing HEAD of the source branch"), - start_sha: z.string().describe("SHA referencing the start commit of the source branch"), - position_type: z.enum(["text", "image", "file"]).describe("Type of position reference"), - new_path: z.string().optional().describe("File path after change"), - old_path: z.string().optional().describe("File path before change"), - new_line: z.number().nullable().optional().describe("Line number after change"), - old_line: z.number().nullable().optional().describe("Line number before change"), - width: z.number().optional().describe("Width of the image (for image diffs)"), - height: z.number().optional().describe("Height of the image (for image diffs)"), - x: z.number().optional().describe("X coordinate on the image (for image diffs)"), - y: z.number().optional().describe("Y coordinate on the image (for image diffs)"), + base_sha: z.string().describe("REQUIRED: Base commit SHA in the source branch. Get this from merge request diff_refs.base_sha."), + head_sha: z.string().describe("REQUIRED: SHA referencing HEAD of the source branch. Get this from merge request diff_refs.head_sha."), + start_sha: z.string().describe("REQUIRED: SHA referencing the start commit of the source branch. Get this from merge request diff_refs.start_sha."), + position_type: z.enum(["text", "image", "file"]).describe("REQUIRED: Position type. Use 'text' for code diffs, 'image' for image diffs, 'file' for file-level comments."), + new_path: z.string().optional().describe("File path after changes. REQUIRED for most diff comments. Use same as old_path if file wasn't renamed."), + old_path: z.string().optional().describe("File path before changes. REQUIRED for most diff comments. Use same as new_path if file wasn't renamed."), + new_line: z.number().nullable().optional().describe("Line number in modified file (after changes). Use for added lines or context lines. NULL for deleted lines. For single-line comments on new lines."), + old_line: z.number().nullable().optional().describe("Line number in original file (before changes). Use for deleted lines or context lines. NULL for added lines. For single-line comments on old lines."), + line_range: LineRangeSchema.optional().describe("MULTILINE COMMENTS: Specify start/end line positions for commenting on multiple lines. Alternative to single old_line/new_line."), + width: z.number().optional().describe("IMAGE DIFFS ONLY: Width of the image (for position_type='image')."), + height: z.number().optional().describe("IMAGE DIFFS ONLY: Height of the image (for position_type='image')."), + x: z.number().optional().describe("IMAGE DIFFS ONLY: X coordinate on the image (for position_type='image')."), + y: z.number().optional().describe("IMAGE DIFFS ONLY: Y coordinate on the image (for position_type='image')."), }); // Schema for creating a new merge request thread @@ -1330,6 +1361,34 @@ export const PromoteProjectMilestoneSchema = GetProjectMilestoneSchema; // Schema for getting burndown chart events for a milestone export const GetMilestoneBurndownEventsSchema = GetProjectMilestoneSchema.merge(PaginationOptionsSchema); +// Add schemas for commit operations +export const ListCommitsSchema = z.object({ + project_id: z.string().describe("Project ID or complete URL-encoded path to project"), + ref_name: z.string().optional().describe("The name of a repository branch, tag or revision range, or if not given the default branch"), + since: z.string().optional().describe("Only commits after or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ"), + until: z.string().optional().describe("Only commits before or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ"), + path: z.string().optional().describe("The file path"), + author: z.string().optional().describe("Search commits by commit author"), + all: z.boolean().optional().describe("Retrieve every commit from the repository"), + with_stats: z.boolean().optional().describe("Stats about each commit are added to the response"), + first_parent: z.boolean().optional().describe("Follow only the first parent commit upon seeing a merge commit"), + order: z.enum(["default", "topo"]).optional().describe("List commits in order"), + trailers: z.boolean().optional().describe("Parse and include Git trailers for every commit"), + page: z.number().optional().describe("Page number for pagination (default: 1)"), + per_page: z.number().optional().describe("Number of items per page (max: 100, default: 20)"), +}); + +export const GetCommitSchema = z.object({ + project_id: z.string().describe("Project ID or complete URL-encoded path to project"), + sha: z.string().describe("The commit hash or name of a repository branch or tag"), + stats: z.boolean().optional().describe("Include commit stats"), +}); + +export const GetCommitDiffSchema = z.object({ + project_id: z.string().describe("Project ID or complete URL-encoded path to project"), + sha: z.string().describe("The commit hash or name of a repository branch or tag"), +}); + // Export types export type GitLabAuthor = z.infer; export type GitLabFork = z.infer; @@ -1396,3 +1455,6 @@ export type GetMilestoneBurndownEventsOptions = z.infer; export type GitLabUsersResponse = z.infer; export type PaginationOptions = z.infer; +export type ListCommitsOptions = z.infer; +export type GetCommitOptions = z.infer; +export type GetCommitDiffOptions = z.infer;