Skip to main content
Glama

create_commit

Create commits with file changes in Azure DevOps repositories using search/replace or unified diff formats to manage code updates.

Instructions

Create a commit on an existing branch using file changes.

  • Provide plain branch names (no "refs/heads/").

  • ⚠️ Each file path may appear only once per commit request—combine all edits to a file into a single change entry.

  • Prefer multiple commits when you have sparse or unrelated edits; smaller focused commits keep review context clear.

🎯 RECOMMENDED: Use the SEARCH/REPLACE format (much easier, no line counting!).

Option 1: SEARCH/REPLACE format (EASIEST) Simply provide the exact text to find and replace:

{ "changes": [{ "path": "src/api/services/function-call.ts", "search": "return axios.post(apiUrl, payload, requestConfig);", "replace": "return axios.post(apiUrl, payload, requestConfig).then(r => { processResponse(r); return r; });" }] }

The server fetches the file, performs the replacement, and generates the diff automatically. No line counting, no hunk headers, no context lines needed!

Option 2: UNIFIED DIFF format (Advanced) If you prefer full control, provide complete unified diffs:

  • Each patch MUST have complete hunk headers: @@ -oldStart,oldLines +newStart,newLines @@

  • CRITICAL: Every @@ marker MUST include line numbers. Do NOT use @@ without line ranges.

  • Include 3-5 context lines before and after changes.

  • For deletions: --- a/filepath and +++ /dev/null

  • For additions: --- /dev/null and +++ b/filepath

Example unified diff:

{ "changes": [{ "patch": "diff --git a/file.yaml b/file.yaml\n--- a/file.yaml\n+++ b/file.yaml\n@@ -4,7 +4,7 @@ spec:\n spec:\n type: ClusterIP\n ports:\n- - port: 8080\n+ - port: 9090\n targetPort: http\n" }] }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
projectIdNoThe ID or name of the project (Default: MyProject)
organizationIdNoThe ID or name of the organization (Default: mycompany)
repositoryIdYesThe ID or name of the repository
branchNameYesThe branch to commit to (without "refs/heads/", e.g., "codex/test2-delete-main-py")
commitMessageYesCommit message
changesYesList of file changes as either unified git diffs OR search/replace pairs

Implementation Reference

  • Core handler function implementing the create_commit tool. Handles patch application or search/replace, constructs GitChange array, and creates commit via Azure DevOps Git API.
    export async function createCommit( connection: WebApi, options: CreateCommitOptions, ): Promise<void> { try { const gitApi = await connection.getGitApi(); const branch = await gitApi.getBranch( options.repositoryId, options.branchName, options.projectId, ); const baseCommit = branch?.commit?.commitId; if (!baseCommit) { throw new AzureDevOpsError(`Branch '${options.branchName}' not found`); } const changes: GitChange[] = []; for (const file of options.changes) { // Handle search/replace format by generating a patch let patchString = file.patch; if ( !patchString && file.search !== undefined && file.replace !== undefined ) { if (!file.path) { throw new AzureDevOpsError( 'path is required when using search/replace format', ); } // Fetch current file content let currentContent = ''; try { const stream = await gitApi.getItemContent( options.repositoryId, file.path, options.projectId, undefined, undefined, undefined, undefined, false, { version: options.branchName, versionType: GitVersionType.Branch }, true, ); currentContent = stream ? await streamToString(stream) : ''; } catch { // File might not exist (new file scenario) - treat as empty currentContent = ''; } // Perform the replacement if (!currentContent.includes(file.search)) { throw new AzureDevOpsError( `Search string not found in ${file.path}. The file may have been modified since you last read it.`, ); } const newContent = currentContent.replace(file.search, file.replace); // Generate proper unified diff patchString = createTwoFilesPatch( file.path, file.path, currentContent, newContent, undefined, undefined, ); } if (!patchString) { throw new AzureDevOpsError( 'Either patch or both search and replace must be provided for each change', ); } const patches = parsePatch(patchString); if (patches.length !== 1) { throw new AzureDevOpsError( `Expected a single file diff for change but received ${patches.length}`, ); } const patch = patches[0]; const normalizePath = (path?: string | null): string | undefined => { if (!path || path === '/dev/null') { return undefined; } return path.replace(/^a\//, '').replace(/^b\//, ''); }; const oldPath = normalizePath(patch.oldFileName); const newPath = normalizePath(patch.newFileName); const targetPath = file.path ?? newPath ?? oldPath; if (!targetPath) { throw new AzureDevOpsError( 'Unable to determine target path for change', ); } if (oldPath && newPath && oldPath !== newPath) { throw new AzureDevOpsError( `Renaming files is not supported (attempted ${oldPath} -> ${newPath})`, ); } let originalContent = ''; if (oldPath) { const stream = await gitApi.getItemContent( options.repositoryId, oldPath, options.projectId, undefined, undefined, undefined, undefined, false, { version: options.branchName, versionType: GitVersionType.Branch }, true, ); originalContent = stream ? await streamToString(stream) : ''; } const patchedContent = applyPatch(originalContent, patch); if (patchedContent === false) { throw new AzureDevOpsError( `Failed to apply diff for ${targetPath}. Please ensure the patch is up to date with the branch head.`, ); } if (!newPath) { changes.push({ changeType: VersionControlChangeType.Delete, item: { path: targetPath }, }); continue; } const changeType = oldPath ? VersionControlChangeType.Edit : VersionControlChangeType.Add; changes.push({ changeType, item: { path: targetPath }, newContent: { content: patchedContent, contentType: ItemContentType.RawText, }, }); } const commit = { comment: options.commitMessage, changes, }; const refUpdate: GitRefUpdate = { name: `refs/heads/${options.branchName}`, oldObjectId: baseCommit, }; await gitApi.createPush( { commits: [commit], refUpdates: [refUpdate] }, options.repositoryId, options.projectId, ); } catch (error) { if (error instanceof AzureDevOpsError) { throw error; } throw new Error( `Failed to create commit: ${error instanceof Error ? error.message : String(error)}`, ); } }
  • Zod input validation schema for the create_commit tool, defining parameters like repositoryId, branchName, commitMessage, and changes (supporting unified diff patches or search/replace format).
    export const CreateCommitSchema = z .object({ projectId: z .string() .optional() .describe(`The ID or name of the project (Default: ${defaultProject})`), organizationId: z .string() .optional() .describe(`The ID or name of the organization (Default: ${defaultOrg})`), repositoryId: z.string().describe('The ID or name of the repository'), branchName: z .string() .describe( 'The branch to commit to (without "refs/heads/", e.g., "codex/test2-delete-main-py")', ), commitMessage: z.string().describe('Commit message'), changes: z .array( z .object({ path: z .string() .optional() .describe( 'File path. Optional for patch format (uses diff header), REQUIRED for search/replace format', ), patch: z .string() .optional() .describe( [ 'Unified git diff for a single file.', 'MUST include `diff --git`, `--- a/...`, `+++ b/...`, and complete hunk headers.', 'CRITICAL: Every hunk header must have line numbers in format: @@ -oldStart,oldLines +newStart,newLines @@', 'Do NOT use @@ without the line range numbers - this will cause parsing failures.', 'Include 3-5 context lines before and after changes for proper patch application.', 'Use `/dev/null` with `---` for new files, or with `+++` for deleted files.', '', 'Example modify patch:', '```diff', 'diff --git a/charts/bcs-mcp-server/templates/service-api.yaml b/charts/bcs-mcp-server/templates/service-api.yaml', '--- a/charts/bcs-mcp-server/templates/service-api.yaml', '+++ b/charts/bcs-mcp-server/templates/service-api.yaml', '@@ -4,7 +4,7 @@ spec:', ' spec:', ' type: {{ .Values.service.type }}', ' ports:', '- - port: 8080', '+ - port: 9090', ' targetPort: deployment-port', ' protocol: TCP', ' name: http', '```', ].join('\n'), ), search: z .string() .optional() .describe( [ 'Alternative to patch: Exact text to search for in the file.', 'Must be used with "replace" and "path" fields.', 'The server will fetch the file, perform the replacement, and generate the patch automatically.', 'This is MUCH EASIER than creating unified diffs manually - no line counting needed!', '', 'Example:', '"search": "return axios.post(apiUrl, payload, requestConfig);"', '"replace": "return axios.post(apiUrl, payload, requestConfig).then(r => { /* process */ return r; });"', ].join('\n'), ), replace: z .string() .optional() .describe( 'Alternative to patch: Exact text to replace the "search" string with. Must be used together with "search" and "path".', ), }) .refine( (data) => { const hasPatch = !!data.patch; const hasSearchReplace = !!data.search && !!data.replace; return hasPatch || hasSearchReplace; }, { message: 'Either "patch" or both "search" and "replace" must be provided', }, ), ) .describe( 'List of file changes as either unified git diffs OR search/replace pairs', ), }) .describe( [ 'Create a commit on an existing branch using file changes.', '- Provide plain branch names (no "refs/heads/").', '', '**RECOMMENDED: Use search/replace format (easier, no line counting needed!)**', '', 'Option 1 - Search/Replace (Easiest):', '```json', '{', ' "changes": [{', ' "path": "src/file.ts",', ' "search": "old code here",', ' "replace": "new code here"', ' }]', '}', '```', '', 'Option 2 - Unified Diff (Advanced):', '- Requires complete hunk headers: @@ -oldStart,oldLines +newStart,newLines @@', '- Include 3-5 context lines before/after changes', '- For deletions: --- a/file, +++ /dev/null', '- For additions: --- /dev/null, +++ b/file', ].join('\n'), );
  • MCP tool registration definition for 'create_commit', including name, detailed description with usage examples, and JSON schema derived from Zod schema.
    { name: 'create_commit', description: [ 'Create a commit on an existing branch using file changes.', '- Provide plain branch names (no "refs/heads/").', '- ⚠️ Each file path may appear only once per commit request—combine all edits to a file into a single change entry.', '- Prefer multiple commits when you have sparse or unrelated edits; smaller focused commits keep review context clear.', '', '🎯 RECOMMENDED: Use the SEARCH/REPLACE format (much easier, no line counting!).', '', '**Option 1: SEARCH/REPLACE format (EASIEST)**', 'Simply provide the exact text to find and replace:', '```json', '{', ' "changes": [{', ' "path": "src/api/services/function-call.ts",', ' "search": "return axios.post(apiUrl, payload, requestConfig);",', ' "replace": "return axios.post(apiUrl, payload, requestConfig).then(r => { processResponse(r); return r; });"', ' }]', '}', '```', 'The server fetches the file, performs the replacement, and generates the diff automatically.', 'No line counting, no hunk headers, no context lines needed!', '', '**Option 2: UNIFIED DIFF format (Advanced)**', 'If you prefer full control, provide complete unified diffs:', '- Each patch MUST have complete hunk headers: @@ -oldStart,oldLines +newStart,newLines @@', '- CRITICAL: Every @@ marker MUST include line numbers. Do NOT use @@ without line ranges.', '- Include 3-5 context lines before and after changes.', '- For deletions: `--- a/filepath` and `+++ /dev/null`', '- For additions: `--- /dev/null` and `+++ b/filepath`', '', 'Example unified diff:', '```json', '{', ' "changes": [{', ' "patch": "diff --git a/file.yaml b/file.yaml\\n--- a/file.yaml\\n+++ b/file.yaml\\n@@ -4,7 +4,7 @@ spec:\\n spec:\\n type: ClusterIP\\n ports:\\n- - port: 8080\\n+ - port: 9090\\n targetPort: http\\n"', ' }]', '}', '```', ].join('\n'), inputSchema: zodToJsonSchema(CreateCommitSchema), },
  • Dispatcher handler in repositories index that routes 'create_commit' tool calls: parses arguments with schema and invokes the core createCommit function.
    case 'create_commit': { const args = CreateCommitSchema.parse(request.params.arguments); await createCommit(connection, { ...args, projectId: args.projectId ?? defaultProject, }); return { content: [{ type: 'text', text: 'Commit created successfully' }], }; }
  • TypeScript interfaces defining FileChange and CreateCommitOptions used by the handler and schema.
    export interface FileChange { /** * Optional path hint for the change. If omitted, the path from the diff * header will be used. */ path?: string; /** Unified diff patch representing the change */ patch?: string; /** * Alternative to patch: exact string to search for in the file. * Must be used together with 'replace'. The server will generate the diff. */ search?: string; /** * Alternative to patch: exact string to replace 'search' with. * Must be used together with 'search'. The server will generate the diff. */ replace?: string; } /** * Options for creating a commit with multiple file changes */ export interface CreateCommitOptions { projectId: string; repositoryId: string; branchName: string; commitMessage: string; changes: FileChange[]; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Tiberriver256/mcp-server-azure-devops'

If you have feedback or need assistance with the MCP directory API, please join our Discord server