Skip to main content
Glama
git-command-executor.ts33.8 kB
/** * Git Command Executor * * Specialized utility for executing Git commands with proper error handling, * repository validation, and Git-specific operations. */ import { TerminalController, CommandResult } from './terminal-controller.js'; import path from 'path'; export interface GitCommandResult extends CommandResult { isGitRepository?: boolean; gitError?: { type: 'not_a_repository' | 'command_not_found' | 'authentication' | 'network' | 'merge_conflict' | 'invalid_ref' | 'unknown'; suggestion?: string; }; } export interface GitStatus { branch: string; ahead: number; behind: number; staged: string[]; unstaged: string[]; untracked: string[]; conflicted: string[]; modified: string[]; added: string[]; clean: boolean; } export class GitCommandExecutor { private terminal: TerminalController; constructor() { this.terminal = new TerminalController(); } /** * Execute Git command with enhanced error handling */ async executeGitCommand( command: string, args: string[] = [], projectPath?: string, options: { timeout?: number; env?: Record<string, string> } = {} ): Promise<GitCommandResult> { // Validate Git is available const gitAvailable = await this.terminal.commandExists('git'); if (!gitAvailable) { return { success: false, stdout: '', stderr: 'Git command not found. Please install Git.', exitCode: 127, executionTime: 0, command: `git ${command} ${args.join(' ')}`, gitError: { type: 'command_not_found', suggestion: 'Install Git from https://git-scm.com/' } }; } // Validate project path if provided if (projectPath) { const dirValidation = await this.terminal.validateDirectory(projectPath); if (!dirValidation.exists) { return { success: false, stdout: '', stderr: `Directory does not exist: ${projectPath}`, exitCode: 1, executionTime: 0, command: `git ${command} ${args.join(' ')}`, gitError: { type: 'not_a_repository', suggestion: 'Ensure the project path exists and is accessible' } }; } } // Execute Git command const result = await this.terminal.executeCommand( 'git', [command, ...args], { cwd: projectPath, timeout: options.timeout || 30000, env: options.env } ); // Enhance result with Git-specific error analysis const gitResult: GitCommandResult = { ...result, isGitRepository: await this.isGitRepository(projectPath), gitError: this.analyzeGitError(result) }; return gitResult; } /** * Check if directory is a Git repository */ async isGitRepository(projectPath?: string): Promise<boolean> { try { const result = await this.terminal.executeCommand( 'git', ['rev-parse', '--git-dir'], { cwd: projectPath } ); return result.success; } catch { return false; } } /** * Initialize Git repository */ async initRepository(projectPath: string, bare: boolean = false): Promise<GitCommandResult> { const args = bare ? ['--bare'] : []; return this.executeGitCommand('init', args, projectPath); } /** * Get repository status */ async getStatus(projectPath: string): Promise<GitCommandResult & { parsedStatus?: GitStatus }> { const result = await this.executeGitCommand('status', ['--porcelain', '-b'], projectPath); if (result.success) { const parsedStatus = this.parseGitStatus(result.stdout); return { ...result, parsedStatus }; } return result; } /** * Add files to staging area */ async addFiles(projectPath: string, files: string[] = ['.']): Promise<GitCommandResult> { return this.executeGitCommand('add', files, projectPath); } /** * Commit changes */ async commit( projectPath: string, message: string, options: { amend?: boolean; signoff?: boolean; author?: string; } = {} ): Promise<GitCommandResult> { const args = ['-m', message]; if (options.amend) args.push('--amend'); if (options.signoff) args.push('--signoff'); if (options.author) args.push('--author', options.author); return this.executeGitCommand('commit', args, projectPath); } /** * Push changes to remote */ async push( projectPath: string, remote: string = 'origin', branch?: string, options: { force?: boolean; setUpstream?: boolean; } = {} ): Promise<GitCommandResult> { const args = [remote]; if (branch) args.push(branch); if (options.force) args.push('--force'); if (options.setUpstream) args.push('--set-upstream'); return this.executeGitCommand('push', args, projectPath); } /** * Pull changes from remote */ async pull( projectPath: string, remote: string = 'origin', branch?: string, options: { rebase?: boolean; force?: boolean; } = {} ): Promise<GitCommandResult> { const args = [remote]; if (branch) args.push(branch); if (options.rebase) args.push('--rebase'); if (options.force) args.push('--force'); return this.executeGitCommand('pull', args, projectPath); } /** * Fetch from remote */ async fetch( projectPath: string, remote: string = 'origin', options: { all?: boolean; prune?: boolean; } = {} ): Promise<GitCommandResult> { const args = [remote]; if (options.all) args.push('--all'); if (options.prune) args.push('--prune'); return this.executeGitCommand('fetch', args, projectPath); } /** * Get current branch name */ async getCurrentBranch(projectPath: string): Promise<GitCommandResult & { branch?: string }> { const result = await this.executeGitCommand('branch', ['--show-current'], projectPath); return { ...result, branch: result.success ? result.stdout.trim() : undefined }; } /** * Get remote URL */ async getRemoteUrl(projectPath: string, remote: string = 'origin'): Promise<GitCommandResult & { url?: string }> { const result = await this.executeGitCommand('remote', ['get-url', remote], projectPath); return { ...result, url: result.success ? result.stdout.trim() : undefined }; } /** * Clone repository */ async cloneRepository( url: string, targetPath: string, options: { branch?: string; depth?: number; recursive?: boolean; } = {} ): Promise<GitCommandResult> { const args = [url, targetPath]; if (options.branch) args.push('--branch', options.branch); if (options.depth) args.push('--depth', options.depth.toString()); if (options.recursive) args.push('--recursive'); return this.executeGitCommand('clone', args); } /** * Create and archive repository backup */ async createBackup( projectPath: string, backupPath: string, format: 'tar' | 'zip' = 'tar' ): Promise<GitCommandResult> { const extension = format === 'zip' ? 'zip' : 'tar.gz'; const outputFile = `${backupPath}.${extension}`; const formatArg = format === 'zip' ? 'zip' : 'tar.gz'; return this.executeGitCommand( 'archive', ['--format', formatArg, '--output', outputFile, 'HEAD'], projectPath ); } /** * Verify that a ref (branch/tag/commit) exists in the repository */ async verifyRef(projectPath: string, ref: string): Promise<GitCommandResult> { return this.executeGitCommand('rev-parse', ['--verify', `${ref}^{commit}`], projectPath); } /** * List branches */ async listBranches( projectPath: string, options: { all?: boolean; remote?: string; } = {} ): Promise<GitCommandResult & { branches?: string[]; remoteBranches?: string[] }> { const args = []; if (options.all) args.push('-a'); if (options.remote) args.push('-r'); const result = await this.executeGitCommand('branch', args, projectPath); if (result.success) { const lines = result.stdout.split('\n').filter(line => line.trim()); const branches: string[] = []; const remoteBranches: string[] = []; for (const line of lines) { const cleanLine = line.replace(/^\*?\s+/, '').trim(); if (cleanLine.startsWith('remotes/')) { remoteBranches.push(cleanLine.replace('remotes/', '')); } else if (cleanLine && !cleanLine.includes('->')) { branches.push(cleanLine); } } return { ...result, branches: options.remote ? undefined : branches, remoteBranches: options.all || options.remote ? remoteBranches : undefined }; } return result; } /** * Create branch */ async createBranch( projectPath: string, branchName: string, sourceBranch?: string ): Promise<GitCommandResult> { const args = [branchName]; if (sourceBranch) args.push(sourceBranch); return this.executeGitCommand('branch', args, projectPath); } /** * Delete branch */ async deleteBranch( projectPath: string, branchName: string, force: boolean = false ): Promise<GitCommandResult> { const args = [force ? '-D' : '-d', branchName]; return this.executeGitCommand('branch', args, projectPath); } /** * Checkout branch */ async checkoutBranch( projectPath: string, branchName: string, options: { create?: boolean; force?: boolean; } = {} ): Promise<GitCommandResult> { const args = [branchName]; if (options.create) args.unshift('-b'); if (options.force) args.push('--force'); return this.executeGitCommand('checkout', args, projectPath); } /** * Get branch information */ async getBranchInfo( projectPath: string, branchName: string ): Promise<GitCommandResult & { exists?: boolean; isRemote?: boolean; lastCommit?: string; upstream?: string; ahead?: number; behind?: number; }> { // Check if branch exists const listResult = await this.listBranches(projectPath, { all: true }); if (!listResult.success) { return listResult; } const allBranches = [...(listResult.branches || []), ...(listResult.remoteBranches || [])]; const exists = allBranches.some(branch => branch === branchName || branch.endsWith(`/${branchName}`) ); if (!exists) { return { success: true, stdout: '', stderr: '', exitCode: 0, executionTime: 0, command: `git branch info ${branchName}`, exists: false }; } // Get last commit const logResult = await this.executeGitCommand( 'log', ['-1', '--format=%H %s', branchName], projectPath ); // Get upstream info const upstreamResult = await this.executeGitCommand( 'rev-parse', ['--abbrev-ref', `${branchName}@{upstream}`], projectPath ); // Get ahead/behind info if upstream exists let ahead = 0; let behind = 0; if (upstreamResult.success) { const countResult = await this.executeGitCommand( 'rev-list', ['--left-right', '--count', `${branchName}...${upstreamResult.stdout.trim()}`], projectPath ); if (countResult.success) { const counts = countResult.stdout.trim().split('\t'); ahead = parseInt(counts[0]) || 0; behind = parseInt(counts[1]) || 0; } } const isRemote = (listResult.remoteBranches || []).some(branch => branch === branchName || branch.endsWith(`/${branchName}`) ); return { success: true, stdout: logResult.stdout, stderr: '', exitCode: 0, executionTime: logResult.executionTime, command: `git branch info ${branchName}`, exists: true, isRemote, lastCommit: logResult.success ? logResult.stdout.trim() : undefined, upstream: upstreamResult.success ? upstreamResult.stdout.trim() : undefined, ahead, behind }; } /** * Get branch commits */ async getBranchCommits( projectPath: string, branchName: string, limit: number = 10 ): Promise<GitCommandResult & { commits?: Array<{ hash: string; message: string; author: string; date: string }> }> { const result = await this.executeGitCommand( 'log', ['-n', limit.toString(), '--format=%H|%s|%an|%ad', '--date=iso', branchName], projectPath ); if (result.success) { const commits = result.stdout .split('\n') .filter(line => line.trim()) .map(line => { const [hash, message, author, date] = line.split('|'); return { hash, message, author, date }; }); return { ...result, commits }; } return result; } /** * Merge branch */ async mergeBranch( projectPath: string, branchName: string, options: { force?: boolean; noFf?: boolean; squash?: boolean; } = {} ): Promise<GitCommandResult & { mergeCommit?: string; conflicts?: string[] }> { const args = [branchName]; if (options.force) args.push('--force'); if (options.noFf) args.push('--no-ff'); if (options.squash) args.push('--squash'); const result = await this.executeGitCommand('merge', args, projectPath); if (result.success) { // Get merge commit hash const commitResult = await this.executeGitCommand('rev-parse', ['HEAD'], projectPath); return { ...result, mergeCommit: commitResult.success ? commitResult.stdout.trim() : undefined, conflicts: [] }; } else { // Check for conflicts const statusResult = await this.getStatus(projectPath); const conflicts = statusResult.parsedStatus?.conflicted || []; return { ...result, conflicts }; } } /** * Compare branches */ async compareBranches( projectPath: string, baseBranch: string, compareBranch: string ): Promise<GitCommandResult & { ahead?: number; behind?: number; commits?: Array<{ hash: string; message: string; author: string; date: string }>; files?: Array<{ file: string; status: string; insertions: number; deletions: number }>; insertions?: number; deletions?: number; }> { // Get ahead/behind count const countResult = await this.executeGitCommand( 'rev-list', ['--left-right', '--count', `${baseBranch}...${compareBranch}`], projectPath ); let behind = 0; let ahead = 0; if (countResult.success) { const counts = countResult.stdout.trim().split('\t'); behind = parseInt(counts[0]) || 0; ahead = parseInt(counts[1]) || 0; } // Get commits const commitsResult = await this.executeGitCommand( 'log', ['--format=%H|%s|%an|%ad', '--date=iso', `${baseBranch}..${compareBranch}`], projectPath ); const commits = commitsResult.success ? commitsResult.stdout .split('\n') .filter(line => line.trim()) .map(line => { const [hash, message, author, date] = line.split('|'); return { hash, message, author, date }; }) : []; // Get file changes const diffResult = await this.executeGitCommand( 'diff', ['--numstat', `${baseBranch}...${compareBranch}`], projectPath ); const files: Array<{ file: string; status: string; insertions: number; deletions: number }> = []; let totalInsertions = 0; let totalDeletions = 0; if (diffResult.success) { const lines = diffResult.stdout.split('\n').filter(line => line.trim()); for (const line of lines) { const parts = line.split('\t'); if (parts.length >= 3) { const insertions = parseInt(parts[0]) || 0; const deletions = parseInt(parts[1]) || 0; const file = parts[2]; files.push({ file, status: insertions > 0 && deletions > 0 ? 'modified' : insertions > 0 ? 'added' : 'deleted', insertions, deletions }); totalInsertions += insertions; totalDeletions += deletions; } } } return { success: true, stdout: `${ahead} commits ahead, ${behind} commits behind`, stderr: '', exitCode: 0, executionTime: countResult.executionTime + commitsResult.executionTime + diffResult.executionTime, command: `git compare ${baseBranch}...${compareBranch}`, ahead, behind, commits, files, insertions: totalInsertions, deletions: totalDeletions }; } /** * List tags */ async listTags( projectPath: string, options: { pattern?: string; sort?: string; } = {} ): Promise<GitCommandResult & { tags?: string[] }> { const args = ['tag']; if (options.pattern) args.push('-l', options.pattern); if (options.sort) args.push('--sort', options.sort); const result = await this.executeGitCommand('tag', args.slice(1), projectPath); if (result.success) { const tags = result.stdout .split('\n') .map(line => line.trim()) .filter(line => line); return { ...result, tags }; } return result; } /** * Create tag */ async createTag( projectPath: string, tagName: string, options: { message?: string; commit?: string; annotated?: boolean; force?: boolean; } = {} ): Promise<GitCommandResult> { const args = [tagName]; if (options.force) args.unshift('-f'); if (options.annotated || options.message) { args.unshift('-a'); if (options.message) { args.push('-m', options.message); } } if (options.commit) args.push(options.commit); return this.executeGitCommand('tag', args, projectPath); } /** * Delete tag */ async deleteTag( projectPath: string, tagName: string, options: { force?: boolean; } = {} ): Promise<GitCommandResult> { // Note: git tag -d doesn't have a --force option // The force parameter is kept for API compatibility but ignored const args = ['-d', tagName]; return this.executeGitCommand('tag', args, projectPath); } /** * Delete remote tag */ async deleteRemoteTag( projectPath: string, remote: string, tagName: string ): Promise<GitCommandResult> { return this.executeGitCommand('push', [remote, '--delete', tagName], projectPath); } /** * Get tag information */ async getTagInfo( projectPath: string, tagName: string ): Promise<GitCommandResult & { exists?: boolean; type?: 'lightweight' | 'annotated'; commit?: string; message?: string; tagger?: string; date?: string; }> { // Check if tag exists const listResult = await this.listTags(projectPath); if (!listResult.success) { return listResult; } const exists = (listResult.tags || []).includes(tagName); if (!exists) { return { success: true, stdout: '', stderr: '', exitCode: 0, executionTime: 0, command: `git tag info ${tagName}`, exists: false }; } // Get tag object type const typeResult = await this.executeGitCommand( 'cat-file', ['-t', tagName], projectPath ); const isAnnotated = typeResult.success && typeResult.stdout.trim() === 'tag'; // Get commit hash const commitResult = await this.executeGitCommand( 'rev-list', ['-n', '1', tagName], projectPath ); let message = ''; let tagger = ''; let date = ''; if (isAnnotated) { // Get annotated tag information const showResult = await this.executeGitCommand( 'show', ['-s', '--format=%B%n---TAGGER---%n%an%n---DATE---%n%ad', '--date=iso', tagName], projectPath ); if (showResult.success) { const parts = showResult.stdout.split('---TAGGER---'); if (parts.length > 1) { message = parts[0].trim(); const taggerParts = parts[1].split('---DATE---'); if (taggerParts.length > 1) { tagger = taggerParts[0].trim(); date = taggerParts[1].trim(); } } } } return { success: true, stdout: `Tag: ${tagName}`, stderr: '', exitCode: 0, executionTime: typeResult.executionTime + commitResult.executionTime, command: `git tag info ${tagName}`, exists: true, type: isAnnotated ? 'annotated' : 'lightweight', commit: commitResult.success ? commitResult.stdout.trim() : undefined, message: message || undefined, tagger: tagger || undefined, date: date || undefined }; } /** * Get commit information */ async getCommitInfo( projectPath: string, commitHash: string ): Promise<GitCommandResult & { hash?: string; author?: string; date?: string; message?: string; }> { const result = await this.executeGitCommand( 'show', ['-s', '--format=%H|%an|%ad|%s', '--date=iso', commitHash], projectPath ); if (result.success) { const [hash, author, date, message] = result.stdout.trim().split('|'); return { ...result, hash, author, date, message }; } return result; } /** * Get Git configuration value */ async getConfig( projectPath: string, key: string, options: { global?: boolean; local?: boolean; system?: boolean; } = {} ): Promise<GitCommandResult & { value?: string }> { const args = ['config']; if (options.global) args.push('--global'); if (options.local) args.push('--local'); if (options.system) args.push('--system'); args.push(key); const result = await this.executeGitCommand('config', args.slice(1), projectPath); return { ...result, value: result.success ? result.stdout.trim() : undefined }; } /** * Set Git configuration value */ async setConfig( projectPath: string, key: string, value: string, options: { global?: boolean; local?: boolean; system?: boolean; } = {} ): Promise<GitCommandResult> { const args = ['config']; if (options.global) args.push('--global'); if (options.local) args.push('--local'); if (options.system) args.push('--system'); args.push(key, value); return this.executeGitCommand('config', args.slice(1), projectPath); } /** * Unset Git configuration value */ async unsetConfig( projectPath: string, key: string, options: { global?: boolean; local?: boolean; system?: boolean; } = {} ): Promise<GitCommandResult> { const args = ['config']; if (options.global) args.push('--global'); if (options.local) args.push('--local'); if (options.system) args.push('--system'); args.push('--unset', key); return this.executeGitCommand('config', args.slice(1), projectPath); } /** * List Git configuration */ async listConfig( projectPath: string, options: { global?: boolean; local?: boolean; system?: boolean; showOrigin?: boolean; } = {} ): Promise<GitCommandResult & { configs?: Array<{ key: string; value: string; origin?: string }> }> { const args = ['config']; if (options.global) args.push('--global'); if (options.local) args.push('--local'); if (options.system) args.push('--system'); if (options.showOrigin) args.push('--show-origin'); args.push('--list'); const result = await this.executeGitCommand('config', args.slice(1), projectPath); if (result.success) { const configs: Array<{ key: string; value: string; origin?: string }> = []; const lines = result.stdout.split('\n').filter(line => line.trim()); for (const line of lines) { if (options.showOrigin) { const match = line.match(/^([^\t]+)\t(.+?)=(.*)$/); if (match) { const [, origin, key, value] = match; configs.push({ key, value, origin }); } } else { const equalIndex = line.indexOf('='); if (equalIndex > 0) { const key = line.substring(0, equalIndex); const value = line.substring(equalIndex + 1); configs.push({ key, value }); } } } return { ...result, configs }; } return result; } /** * Edit Git configuration */ async editConfig( projectPath: string, options: { global?: boolean; local?: boolean; system?: boolean; } = {} ): Promise<GitCommandResult> { const args = ['config']; if (options.global) args.push('--global'); if (options.local) args.push('--local'); if (options.system) args.push('--system'); args.push('--edit'); return this.executeGitCommand('config', args.slice(1), projectPath); } /** * Show Git configuration with details */ async showConfig( projectPath: string, key?: string, options: { global?: boolean; local?: boolean; system?: boolean; showOrigin?: boolean; showScope?: boolean; } = {} ): Promise<GitCommandResult & { configs?: Array<{ key: string; value: string; origin?: string; scope?: string; }> }> { const args = ['config']; if (options.global) args.push('--global'); if (options.local) args.push('--local'); if (options.system) args.push('--system'); if (options.showOrigin) args.push('--show-origin'); if (options.showScope) args.push('--show-scope'); if (key) { args.push(key); } else { args.push('--list'); } const result = await this.executeGitCommand('config', args.slice(1), projectPath); if (result.success) { const configs: Array<{ key: string; value: string; origin?: string; scope?: string }> = []; if (key) { // Single key result configs.push({ key, value: result.stdout.trim(), origin: options.showOrigin ? 'unknown' : undefined, scope: options.showScope ? 'unknown' : undefined }); } else { // List result const lines = result.stdout.split('\n').filter(line => line.trim()); for (const line of lines) { if (options.showOrigin && options.showScope) { const match = line.match(/^([^\t]+)\t([^\t]+)\t(.+?)=(.*)$/); if (match) { const [, origin, scope, key, value] = match; configs.push({ key, value, origin, scope }); } } else if (options.showOrigin) { const match = line.match(/^([^\t]+)\t(.+?)=(.*)$/); if (match) { const [, origin, key, value] = match; configs.push({ key, value, origin }); } } else if (options.showScope) { const match = line.match(/^([^\t]+)\t(.+?)=(.*)$/); if (match) { const [, scope, key, value] = match; configs.push({ key, value, scope }); } } else { const equalIndex = line.indexOf('='); if (equalIndex > 0) { const key = line.substring(0, equalIndex); const value = line.substring(equalIndex + 1); configs.push({ key, value }); } } } } return { ...result, configs }; } return result; } /** * Parse Git status output */ private parseGitStatus(statusOutput: string): GitStatus { const lines = statusOutput.split('\n').filter(line => line.trim()); const branchLine = lines[0]; // Parse branch info let branch = 'main'; let ahead = 0; let behind = 0; if (branchLine.startsWith('## ')) { const branchInfo = branchLine.substring(3); const parts = branchInfo.split('...'); branch = parts[0]; if (parts[1]) { const trackingInfo = parts[1]; const aheadMatch = trackingInfo.match(/ahead (\d+)/); const behindMatch = trackingInfo.match(/behind (\d+)/); if (aheadMatch) ahead = parseInt(aheadMatch[1]); if (behindMatch) behind = parseInt(behindMatch[1]); } } // Parse file status const staged: string[] = []; const unstaged: string[] = []; const untracked: string[] = []; const conflicted: string[] = []; const modified: string[] = []; const added: string[] = []; for (let i = 1; i < lines.length; i++) { const line = lines[i]; if (line.length < 3) continue; const indexStatus = line[0]; const workTreeStatus = line[1]; const fileName = line.substring(3); if (indexStatus === 'U' || workTreeStatus === 'U' || (indexStatus === 'A' && workTreeStatus === 'A') || (indexStatus === 'D' && workTreeStatus === 'D')) { conflicted.push(fileName); } else if (indexStatus !== ' ' && indexStatus !== '?') { staged.push(fileName); if (indexStatus === 'A') { added.push(fileName); } else if (indexStatus === 'M') { modified.push(fileName); } } else if (workTreeStatus !== ' ' && workTreeStatus !== '?') { unstaged.push(fileName); if (workTreeStatus === 'M') { modified.push(fileName); } } else if (indexStatus === '?' && workTreeStatus === '?') { untracked.push(fileName); } } return { branch, ahead, behind, staged, unstaged, untracked, conflicted, modified, added, clean: staged.length === 0 && unstaged.length === 0 && untracked.length === 0 }; } /** * Reset repository to specific commit/state */ async reset( projectPath: string, options: { type: 'soft' | 'mixed' | 'hard'; commit?: string; paths?: string[]; options?: { keepIndex?: boolean; noRefresh?: boolean; quiet?: boolean; merge?: boolean; keep?: boolean; }; } ): Promise<GitCommandResult> { const args: string[] = [options.type]; // Add commit reference if specified if (options.commit) { args.push(options.commit); } // Add paths for mixed reset with specific files if (options.type === 'mixed' && options.paths && options.paths.length > 0) { args.push('--'); args.push(...options.paths); } // Add additional options if (options.options) { if (options.options.keepIndex) args.push('--keep-index'); if (options.options.noRefresh) args.push('--no-refresh'); if (options.options.quiet) args.push('--quiet'); if (options.options.merge) args.push('--merge'); if (options.options.keep) args.push('--keep'); } return await this.executeGitCommand('reset', args, projectPath); } /** * Analyze Git error and provide suggestions */ private analyzeGitError(result: CommandResult): GitCommandResult['gitError'] { if (result.success) return undefined; const stderr = result.stderr.toLowerCase(); const stdout = result.stdout.toLowerCase(); const errorText = `${stderr} ${stdout}`; if (errorText.includes('not a git repository')) { return { type: 'not_a_repository', suggestion: 'Initialize a Git repository with "git init" or navigate to an existing repository' }; } if (errorText.includes('not a valid object name')) { return { type: 'invalid_ref', suggestion: 'The specified branch/tag/ref does not exist. Verify the ref name and try again.' }; } if (errorText.includes('authentication failed') || errorText.includes('permission denied')) { return { type: 'authentication', suggestion: 'Check your Git credentials and ensure you have access to the repository' }; } if (errorText.includes('network') || errorText.includes('connection') || errorText.includes('timeout')) { return { type: 'network', suggestion: 'Check your internet connection and repository URL' }; } if (errorText.includes('merge conflict') || errorText.includes('conflict')) { return { type: 'merge_conflict', suggestion: 'Resolve merge conflicts and commit the changes' }; } if (errorText.includes('push cannot contain secrets') || errorText.includes('repository rule violations')) { return { type: 'authentication', suggestion: 'Remove sensitive data from commit history using git reset or git filter-branch' }; } if (errorText.includes('fetch --all does not take a repository argument')) { return { type: 'invalid_ref', suggestion: 'Use git fetch <remote> <branch> instead of git fetch --all <args>' }; } return { type: 'unknown', suggestion: 'Check the error message for more details' }; } } // Export singleton instance export const gitCommandExecutor = new GitCommandExecutor();

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/Andre-Buzeli/git-mcp'

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