Skip to main content
Glama

Git MCP Server

by Sheshiyer
remote-operations.ts9.29 kB
import { BaseGitOperation } from '../base/base-operation.js'; import { GitCommandBuilder } from '../../common/command-builder.js'; import { CommandResult } from '../base/operation-result.js'; import { ErrorHandler } from '../../errors/error-handler.js'; import { RepositoryValidator } from '../../utils/repository.js'; import { CommandExecutor } from '../../utils/command.js'; import { RepoStateType } from '../../caching/repository-cache.js'; import { RemoteListOptions, RemoteAddOptions, RemoteRemoveOptions, RemoteSetUrlOptions, RemotePruneOptions, RemoteListResult, RemoteAddResult, RemoteRemoveResult, RemoteSetUrlResult, RemotePruneResult, RemoteConfig } from './remote-types.js'; /** * Handles Git remote listing operations */ export class RemoteListOperation extends BaseGitOperation<RemoteListOptions, RemoteListResult> { protected buildCommand(): GitCommandBuilder { const command = GitCommandBuilder.remote(); if (this.options.verbose) { command.flag('verbose'); } return command; } protected async parseResult(result: CommandResult): Promise<RemoteListResult> { const remotes: RemoteConfig[] = []; const lines = result.stdout.split('\n').filter(Boolean); for (const line of lines) { const [name, url, purpose] = line.split(/\s+/); // Find or create remote config let remote = remotes.find(r => r.name === name); if (!remote) { remote = { name, fetchUrl: url }; remotes.push(remote); } // Set URL based on purpose if (purpose === '(push)') { remote.pushUrl = url; } // Get additional configuration if verbose if (this.options.verbose) { const configResult = await CommandExecutor.executeGitCommand( `config --get-regexp ^remote\\.${name}\\.`, this.context.operation, this.getResolvedPath() ); configResult.stdout.split('\n').filter(Boolean).forEach(configLine => { const [key, value] = configLine.split(' '); const configKey = key.split('.')[2]; switch (configKey) { case 'tagopt': remote!.fetchTags = value === '--tags'; break; case 'mirror': remote!.mirror = value as 'fetch' | 'push'; break; case 'fetch': if (!remote!.branches) remote!.branches = []; const branch = value.match(/refs\/heads\/(.+):refs\/remotes\/.+/)?.[1]; if (branch) remote!.branches.push(branch); break; } }); } } return { remotes, raw: result.stdout }; } protected getCacheConfig() { return { command: 'remote', stateType: RepoStateType.REMOTE }; } protected validateOptions(): void { // No specific validation needed for listing } } /** * Handles Git remote add operations */ export class RemoteAddOperation extends BaseGitOperation<RemoteAddOptions, RemoteAddResult> { protected buildCommand(): GitCommandBuilder { const command = GitCommandBuilder.remote() .arg('add'); if (this.options.fetch) { command.flag('fetch'); } if (typeof this.options.tags === 'boolean') { command.flag(this.options.tags ? 'tags' : 'no-tags'); } if (this.options.mirror) { command.option('mirror', this.options.mirror); } command.arg(this.options.name) .arg(this.options.url); return command; } protected async parseResult(result: CommandResult): Promise<RemoteAddResult> { // Get full remote configuration const listOperation = new RemoteListOperation(this.context, { verbose: true }); const listResult = await listOperation.execute(); const remotes = listResult.data?.remotes; if (!remotes) { throw ErrorHandler.handleOperationError( new Error('Failed to get remote list'), { operation: this.context.operation } ); } const remote = remotes.find(r => r.name === this.options.name); if (!remote) { throw ErrorHandler.handleOperationError( new Error(`Failed to get configuration for remote ${this.options.name}`), { operation: this.context.operation } ); } return { remote, raw: result.stdout }; } protected getCacheConfig() { return { command: 'remote_add', stateType: RepoStateType.REMOTE }; } protected validateOptions(): void { if (!this.options.name) { throw ErrorHandler.handleValidationError( new Error('Remote name is required'), { operation: this.context.operation } ); } if (!this.options.url) { throw ErrorHandler.handleValidationError( new Error('Remote URL is required'), { operation: this.context.operation } ); } } } /** * Handles Git remote remove operations */ export class RemoteRemoveOperation extends BaseGitOperation<RemoteRemoveOptions, RemoteRemoveResult> { protected buildCommand(): GitCommandBuilder { return GitCommandBuilder.remote() .arg('remove') .arg(this.options.name); } protected parseResult(result: CommandResult): RemoteRemoveResult { return { name: this.options.name, raw: result.stdout }; } protected getCacheConfig() { return { command: 'remote_remove', stateType: RepoStateType.REMOTE }; } protected async validateOptions(): Promise<void> { if (!this.options.name) { throw ErrorHandler.handleValidationError( new Error('Remote name is required'), { operation: this.context.operation } ); } // Ensure remote exists await RepositoryValidator.validateRemoteConfig( this.getResolvedPath(), this.options.name, this.context.operation ); } } /** * Handles Git remote set-url operations */ export class RemoteSetUrlOperation extends BaseGitOperation<RemoteSetUrlOptions, RemoteSetUrlResult> { protected buildCommand(): GitCommandBuilder { const command = GitCommandBuilder.remote() .arg('set-url'); if (this.options.pushUrl) { command.flag('push'); } if (this.options.add) { command.flag('add'); } if (this.options.delete) { command.flag('delete'); } command.arg(this.options.name) .arg(this.options.url); return command; } protected async parseResult(result: CommandResult): Promise<RemoteSetUrlResult> { // Get full remote configuration const listOperation = new RemoteListOperation(this.context, { verbose: true }); const listResult = await listOperation.execute(); const remotes = listResult.data?.remotes; if (!remotes) { throw ErrorHandler.handleOperationError( new Error('Failed to get remote list'), { operation: this.context.operation } ); } const remote = remotes.find(r => r.name === this.options.name); if (!remote) { throw ErrorHandler.handleOperationError( new Error(`Failed to get configuration for remote ${this.options.name}`), { operation: this.context.operation } ); } return { remote, raw: result.stdout }; } protected getCacheConfig() { return { command: 'remote_set_url', stateType: RepoStateType.REMOTE }; } protected async validateOptions(): Promise<void> { if (!this.options.name) { throw ErrorHandler.handleValidationError( new Error('Remote name is required'), { operation: this.context.operation } ); } if (!this.options.url) { throw ErrorHandler.handleValidationError( new Error('Remote URL is required'), { operation: this.context.operation } ); } // Ensure remote exists await RepositoryValidator.validateRemoteConfig( this.getResolvedPath(), this.options.name, this.context.operation ); } } /** * Handles Git remote prune operations */ export class RemotePruneOperation extends BaseGitOperation<RemotePruneOptions, RemotePruneResult> { protected buildCommand(): GitCommandBuilder { const command = GitCommandBuilder.remote() .arg('prune'); if (this.options.dryRun) { command.flag('dry-run'); } command.arg(this.options.name); return command; } protected parseResult(result: CommandResult): RemotePruneResult { const prunedBranches = result.stdout .split('\n') .filter(line => line.includes('* [pruned] ')) .map(line => line.match(/\* \[pruned\] (.+)/)?.[1] || ''); return { name: this.options.name, prunedBranches, raw: result.stdout }; } protected getCacheConfig() { return { command: 'remote_prune', stateType: RepoStateType.REMOTE }; } protected async validateOptions(): Promise<void> { if (!this.options.name) { throw ErrorHandler.handleValidationError( new Error('Remote name is required'), { operation: this.context.operation } ); } // Ensure remote exists await RepositoryValidator.validateRemoteConfig( this.getResolvedPath(), this.options.name, this.context.operation ); } }

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/Sheshiyer/git-mcp-v2'

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