Skip to main content
Glama
git-update.ts38.6 kB
import { ToolResult } from '../utils/operation-error-handler'; export interface ToolParams { projectPath: string; provider?: 'github' | 'gitea' | 'both'; [key: string]: any; } import { GitCommandExecutor } from '../utils/git-command-executor'; import { OperationErrorHandler } from '../utils/operation-error-handler'; import { ParameterValidator } from '../utils/parameter-validator'; import { ProviderOperationHandler } from '../providers/provider-operation-handler'; import { Logger } from '../utils/logger'; import { configManager } from '../config.js'; export interface GitUpdateParams extends ToolParams { projectPath: string; action: 'update' | 'history' | 'changelog' | 'track' | 'sync-providers' | 'status' | 'rollback' | 'compare'; // Update parameters updateType?: 'all' | 'dependencies' | 'code' | 'docs' | 'config'; autoCommit?: boolean; commitMessage?: string; createTag?: boolean; tagName?: string; // History parameters since?: string; until?: string; author?: string; format?: 'json' | 'markdown' | 'text'; // Changelog parameters changelogPath?: string; version?: string; includeCommits?: boolean; groupByType?: boolean; // Track parameters trackFile?: string; trackPattern?: string; watchMode?: boolean; // Sync providers parameters providers?: string[]; forceSync?: boolean; // Rollback parameters rollbackTo?: string; rollbackType?: 'commit' | 'tag' | 'version'; // Compare parameters compareWith?: string; compareType?: 'commit' | 'tag' | 'branch' | 'provider'; } export interface UpdateHistory { timestamp: string; action: string; details: string; files: string[]; commitHash?: string; tagName?: string; provider?: string; } export interface ChangelogEntry { version: string; date: string; type: 'feature' | 'fix' | 'breaking' | 'docs' | 'refactor' | 'perf' | 'test' | 'chore'; description: string; commits: string[]; author: string; files: string[]; } export interface ProjectStatus { localStatus: { branch: string; lastCommit: string; uncommittedChanges: number; untrackedFiles: number; }; providerStatus: { [provider: string]: { lastSync: string; ahead: number; behind: number; conflicts: boolean; }; }; updateHistory: UpdateHistory[]; } export class GitUpdateTool { private gitExecutor: GitCommandExecutor; private providerHandler?: ProviderOperationHandler; private logger: Logger; private updateHistory: UpdateHistory[] = []; constructor(providerConfig?: any) { this.gitExecutor = new GitCommandExecutor(); this.logger = new Logger(); if (providerConfig) { this.providerHandler = new ProviderOperationHandler(providerConfig); } } static getToolSchema() { return { name: 'git-update', description: 'Advanced project update tool with history tracking, changelog generation, and multi-provider synchronization', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['update', 'history', 'changelog', 'track', 'sync-providers', 'status', 'rollback', 'compare'], description: 'Update operation to perform' }, projectPath: { type: 'string', description: 'Path to the Git repository' }, provider: { type: 'string', enum: ['github', 'gitea', 'both'], description: 'Provider for remote operations' }, updateType: { type: 'string', enum: ['all', 'dependencies', 'code', 'docs', 'config'], description: 'Type of update to perform' }, autoCommit: { type: 'boolean', description: 'Automatically commit changes' }, commitMessage: { type: 'string', description: 'Custom commit message' }, createTag: { type: 'boolean', description: 'Create a tag after update' }, tagName: { type: 'string', description: 'Name for the tag' }, since: { type: 'string', description: 'Start date for history (ISO format or relative)' }, until: { type: 'string', description: 'End date for history (ISO format or relative)' }, author: { type: 'string', description: 'Filter by author' }, format: { type: 'string', enum: ['json', 'markdown', 'text'], description: 'Output format for history' }, changelogPath: { type: 'string', description: 'Path to changelog file' }, version: { type: 'string', description: 'Version for changelog entry' }, includeCommits: { type: 'boolean', description: 'Include commit details in changelog' }, groupByType: { type: 'boolean', description: 'Group changelog entries by type' }, trackFile: { type: 'string', description: 'File to track for changes' }, trackPattern: { type: 'string', description: 'Pattern to track for changes' }, watchMode: { type: 'boolean', description: 'Enable watch mode for tracking' }, providers: { type: 'array', items: { type: 'string' }, description: 'Providers to sync with' }, forceSync: { type: 'boolean', description: 'Force synchronization' }, rollbackTo: { type: 'string', description: 'Target for rollback (commit, tag, or version)' }, rollbackType: { type: 'string', enum: ['commit', 'tag', 'version'], description: 'Type of rollback target' }, compareWith: { type: 'string', description: 'Target to compare with' }, compareType: { type: 'string', enum: ['commit', 'tag', 'branch', 'provider'], description: 'Type of comparison target' } }, required: ['action', 'projectPath'] } }; } async execute(params: GitUpdateParams): Promise<ToolResult> { const startTime = Date.now(); this.logger.info(`Executing git-update ${params.action}`); try { // Validate parameters const validation = ParameterValidator.validateToolParams('git-update', params); if (!validation.isValid) { return OperationErrorHandler.createToolError( 'VALIDATION_ERROR', 'Parameter validation failed', params.action, validation, validation.suggestions ); } // Route to appropriate handler switch (params.action) { case 'update': return await this.handleUpdate(params, startTime); case 'history': return await this.handleHistory(params, startTime); case 'changelog': return await this.handleChangelog(params, startTime); case 'track': return await this.handleTrack(params, startTime); case 'sync-providers': return await this.handleSyncProviders(params, startTime); case 'status': return await this.handleStatus(params, startTime); case 'rollback': return await this.handleRollback(params, startTime); case 'compare': return await this.handleCompare(params, startTime); default: return OperationErrorHandler.createToolError( 'UNSUPPORTED_OPERATION', `Update operation '${params.action}' is not supported`, params.action, {}, ['Use one of: update, history, changelog, track, sync-providers, status, rollback, compare'] ); } } catch (error) { this.logger.error('Error executing git-update'); return OperationErrorHandler.createToolError( 'EXECUTION_ERROR', `Failed to execute git-update ${params.action}: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { error: error instanceof Error ? error.message : String(error) }, ['Check project path and Git repository status'] ); } } private async handleUpdate(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { const updateType = params.updateType || 'all'; const changes: string[] = []; const files: string[] = []; this.logger.info(`Performing ${updateType} update`); // Perform update based on type switch (updateType) { case 'dependencies': await this.updateDependencies(params.projectPath); changes.push('Updated dependencies'); break; case 'code': await this.updateCode(params.projectPath); changes.push('Updated code files'); break; case 'docs': await this.updateDocs(params.projectPath); changes.push('Updated documentation'); break; case 'config': await this.updateConfig(params.projectPath); changes.push('Updated configuration'); break; case 'all': default: await this.updateAll(params.projectPath); changes.push('Updated all project components'); break; } // Get changed files const statusResult = await this.gitExecutor.executeGitCommand('status', ['--porcelain'], params.projectPath); if (statusResult.success) { const changedFiles = statusResult.stdout .split('\n') .filter(line => line.trim()) .map(line => line.substring(3).trim()); files.push(...changedFiles); } // Auto commit if requested let commitHash: string | undefined; if (params.autoCommit !== false && files.length > 0) { const commitMessage = params.commitMessage || `Update: ${changes.join(', ')}`; const addResult = await this.gitExecutor.executeGitCommand('add', ['.'], params.projectPath); if (addResult.success) { const commitResult = await this.gitExecutor.executeGitCommand('commit', ['-m', commitMessage], params.projectPath); if (commitResult.success) { commitHash = await this.getCurrentCommitHash(params.projectPath); } } } // Create tag if requested let tagName: string | undefined; if (params.createTag && commitHash) { tagName = params.tagName || `update-${Date.now()}`; await this.gitExecutor.executeGitCommand('tag', [tagName], params.projectPath); } // Record in history const historyEntry: UpdateHistory = { timestamp: new Date().toISOString(), action: 'update', details: changes.join(', '), files, commitHash, tagName }; this.updateHistory.push(historyEntry); const executionTime = Date.now() - startTime; return { success: true, data: { updateType, changes, files, commitHash, tagName, executionTime }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime } }; } catch (error) { return OperationErrorHandler.createToolError( 'UPDATE_ERROR', `Failed to perform update: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { updateType: params.updateType }, ['Check repository status and permissions'] ); } } private async handleHistory(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { const since = params.since || '1 week ago'; const until = params.until || 'now'; const author = params.author; const format = params.format || 'json'; this.logger.info(`Getting update history since ${since} until ${until}`); // Get Git log const logArgs = ['--since', since, '--until', until]; if (author) { logArgs.push('--author', author); } logArgs.push('--pretty=format:%H|%an|%ad|%s', '--date=iso'); const logResult = await this.gitExecutor.executeGitCommand('log', logArgs, params.projectPath); if (!logResult.success) { throw new Error('Failed to get Git history'); } const commits = logResult.stdout .split('\n') .filter(line => line.trim()) .map(line => { const [hash, author, date, message] = line.split('|'); return { hash: hash.substring(0, 8), fullHash: hash, author, date, message }; }); // Get update history const filteredHistory = this.updateHistory.filter(entry => { const entryDate = new Date(entry.timestamp); const sinceDate = new Date(since); const untilDate = new Date(until); return entryDate >= sinceDate && entryDate <= untilDate; }); let output: any; switch (format) { case 'markdown': output = this.formatHistoryMarkdown(commits, filteredHistory); break; case 'text': output = this.formatHistoryText(commits, filteredHistory); break; case 'json': default: output = { commits, updateHistory: filteredHistory, summary: { totalCommits: commits.length, totalUpdates: filteredHistory.length, period: { since, until } } }; break; } const executionTime = Date.now() - startTime; return { success: true, data: output, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime } }; } catch (error) { return OperationErrorHandler.createToolError( 'HISTORY_ERROR', `Failed to get history: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { since: params.since, until: params.until }, ['Check date format and repository access'] ); } } private async handleChangelog(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { const changelogPath = params.changelogPath || 'CHANGELOG.md'; const version = params.version || await this.getCurrentVersion(params.projectPath); const includeCommits = params.includeCommits !== false; const groupByType = params.groupByType !== false; this.logger.info(`Generating changelog for version ${version}`); // Get commits since last tag const lastTag = await this.getLastTag(params.projectPath); const sinceRef = lastTag || 'HEAD~10'; const logResult = await this.gitExecutor.executeGitCommand('log', [ `${sinceRef}..HEAD`, '--pretty=format:%H|%an|%ad|%s', '--date=iso' ], params.projectPath); const commits = logResult.stdout .split('\n') .filter(line => line.trim()) .map(line => { const [hash, author, date, message] = line.split('|'); return { hash: hash.substring(0, 8), fullHash: hash, author, date, message, type: this.parseCommitType(message) }; }); // Generate changelog entry const changelogEntry: ChangelogEntry = { version, date: new Date().toISOString().split('T')[0], type: this.determineReleaseType(commits) as 'feature' | 'fix' | 'breaking' | 'docs' | 'refactor' | 'perf' | 'test' | 'chore', description: this.generateReleaseDescription(commits), commits: includeCommits ? commits.map(c => c.message) : [], author: commits.length > 0 ? commits[0].author : 'Unknown', files: await this.getChangedFiles(sinceRef, params.projectPath) }; // Generate changelog content const changelogContent = this.generateChangelogContent(changelogEntry, groupByType); // Write to file const fs = require('fs'); const path = require('path'); const fullPath = path.join(params.projectPath, changelogPath); let existingContent = ''; if (fs.existsSync(fullPath)) { existingContent = fs.readFileSync(fullPath, 'utf8'); } const newContent = this.mergeChangelogContent(changelogContent, existingContent); fs.writeFileSync(fullPath, newContent); const executionTime = Date.now() - startTime; return { success: true, data: { version, changelogPath, entry: changelogEntry, content: changelogContent }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime } }; } catch (error) { return OperationErrorHandler.createToolError( 'CHANGELOG_ERROR', `Failed to generate changelog: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { version: params.version, changelogPath: params.changelogPath }, ['Check repository status and file permissions'] ); } } private async handleTrack(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { const trackFile = params.trackFile; const trackPattern = params.trackPattern; const watchMode = params.watchMode || false; this.logger.info(`Tracking changes${trackFile ? ` for file: ${trackFile}` : ''}${trackPattern ? ` with pattern: ${trackPattern}` : ''}`); if (trackFile) { // Track specific file const fileStatus = await this.trackFile(trackFile, params.projectPath); const historyEntry: UpdateHistory = { timestamp: new Date().toISOString(), action: 'track', details: `Tracked file: ${trackFile}`, files: [trackFile], provider: params.provider || 'both' }; this.updateHistory.push(historyEntry); return { success: true, data: { trackedFile: trackFile, status: fileStatus, watchMode }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime: Date.now() - startTime } }; } else if (trackPattern) { // Track files matching pattern const matchingFiles = await this.trackPattern(trackPattern, params.projectPath); const historyEntry: UpdateHistory = { timestamp: new Date().toISOString(), action: 'track', details: `Tracked pattern: ${trackPattern}`, files: matchingFiles, provider: params.provider || 'both' }; this.updateHistory.push(historyEntry); return { success: true, data: { pattern: trackPattern, matchingFiles, count: matchingFiles.length, watchMode }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime: Date.now() - startTime } }; } else { // Track all changes const allChanges = await this.trackAllChanges(params.projectPath); const historyEntry: UpdateHistory = { timestamp: new Date().toISOString(), action: 'track', details: 'Tracked all changes', files: allChanges, provider: params.provider || 'both' }; this.updateHistory.push(historyEntry); return { success: true, data: { allChanges, count: allChanges.length, watchMode }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime: Date.now() - startTime } }; } } catch (error) { return OperationErrorHandler.createToolError( 'TRACK_ERROR', `Failed to track changes: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { trackFile: params.trackFile, trackPattern: params.trackPattern }, ['Check file paths and repository status'] ); } } private async handleSyncProviders(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { const providers = params.providers || ['github', 'gitea']; const forceSync = params.forceSync || false; this.logger.info(`Syncing with providers: ${providers.join(', ')}`); const syncResults: any = {}; for (const provider of providers) { try { if (this.providerHandler) { // Sync with provider const syncResult = await this.providerHandler.executeOperation({ provider: provider as 'github' | 'gitea' | 'both', operation: 'sync', parameters: { projectPath: params.projectPath, force: forceSync }, requiresAuth: true, isRemoteOperation: true }); syncResults[provider] = { success: true, result: syncResult }; } else { syncResults[provider] = { success: false, error: 'Provider handler not configured' }; } } catch (error) { syncResults[provider] = { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } const historyEntry: UpdateHistory = { timestamp: new Date().toISOString(), action: 'sync-providers', details: `Synced with providers: ${providers.join(', ')}`, files: [], provider: providers.join(',') }; this.updateHistory.push(historyEntry); const executionTime = Date.now() - startTime; return { success: true, data: { providers, results: syncResults, summary: { total: providers.length, successful: Object.values(syncResults).filter((r: any) => r.success).length, failed: Object.values(syncResults).filter((r: any) => !r.success).length } }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime } }; } catch (error) { return OperationErrorHandler.createToolError( 'SYNC_ERROR', `Failed to sync providers: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { providers: params.providers }, ['Check provider configuration and network connectivity'] ); } } private async handleStatus(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { this.logger.info('Getting project status'); // Get local status const branchResult = await this.gitExecutor.executeGitCommand('branch', ['--show-current'], params.projectPath); const lastCommitResult = await this.gitExecutor.executeGitCommand('log', ['-1', '--format=%H|%an|%ad|%s', '--date=iso'], params.projectPath); const statusResult = await this.gitExecutor.executeGitCommand('status', ['--porcelain'], params.projectPath); const localStatus = { branch: branchResult.success ? branchResult.stdout.trim() : 'unknown', lastCommit: lastCommitResult.success ? lastCommitResult.stdout.split('|')[0].substring(0, 8) : 'unknown', uncommittedChanges: statusResult.success ? statusResult.stdout.split('\n').filter(line => line.trim()).length : 0, untrackedFiles: statusResult.success ? statusResult.stdout.split('\n').filter(line => line.startsWith('??')).length : 0 }; // Get provider status const providerStatus: any = {}; if (this.providerHandler) { const providers = ['github', 'gitea']; for (const provider of providers) { try { const statusResult = await this.providerHandler.executeOperation({ provider: provider as 'github' | 'gitea' | 'both', operation: 'status', parameters: { projectPath: params.projectPath }, requiresAuth: true, isRemoteOperation: true }); providerStatus[provider] = { lastSync: new Date().toISOString(), ahead: 0, behind: 0, conflicts: false, ...statusResult }; } catch (error) { providerStatus[provider] = { lastSync: 'never', ahead: 0, behind: 0, conflicts: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } } const projectStatus: ProjectStatus = { localStatus, providerStatus, updateHistory: this.updateHistory.slice(-10) // Last 10 entries }; const executionTime = Date.now() - startTime; return { success: true, data: projectStatus, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime } }; } catch (error) { return OperationErrorHandler.createToolError( 'STATUS_ERROR', `Failed to get status: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, {}, ['Check repository status and provider configuration'] ); } } private async handleRollback(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { const rollbackTo = params.rollbackTo; const rollbackType = params.rollbackType || 'commit'; if (!rollbackTo) { throw new Error('rollbackTo parameter is required'); } this.logger.info(`Rolling back to ${rollbackType}: ${rollbackTo}`); let targetRef: string; switch (rollbackType) { case 'tag': targetRef = rollbackTo; break; case 'version': targetRef = `v${rollbackTo}`; break; case 'commit': default: targetRef = rollbackTo; break; } // Perform rollback const resetResult = await this.gitExecutor.executeGitCommand('reset', ['--hard', targetRef], params.projectPath); if (!resetResult.success) { throw new Error(`Failed to reset to ${targetRef}`); } const historyEntry: UpdateHistory = { timestamp: new Date().toISOString(), action: 'rollback', details: `Rolled back to ${rollbackType}: ${rollbackTo}`, files: [], commitHash: targetRef }; this.updateHistory.push(historyEntry); const executionTime = Date.now() - startTime; return { success: true, data: { rollbackTo, rollbackType, targetRef, resetResult: resetResult.stdout }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime } }; } catch (error) { return OperationErrorHandler.createToolError( 'ROLLBACK_ERROR', `Failed to rollback: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { rollbackTo: params.rollbackTo, rollbackType: params.rollbackType }, ['Check target reference and repository status'] ); } } private async handleCompare(params: GitUpdateParams, startTime: number): Promise<ToolResult> { try { const compareWith = params.compareWith; const compareType = params.compareType || 'commit'; if (!compareWith) { throw new Error('compareWith parameter is required'); } this.logger.info(`Comparing with ${compareType}: ${compareWith}`); // Get diff const diffResult = await this.gitExecutor.executeGitCommand('diff', [compareWith, 'HEAD'], params.projectPath); if (!diffResult.success) { throw new Error('Failed to get diff'); } // Get commit comparison const logResult = await this.gitExecutor.executeGitCommand('log', [ `${compareWith}..HEAD`, '--pretty=format:%H|%an|%ad|%s', '--date=iso' ], params.projectPath); const commits = logResult.stdout .split('\n') .filter(line => line.trim()) .map(line => { const [hash, author, date, message] = line.split('|'); return { hash: hash.substring(0, 8), fullHash: hash, author, date, message }; }); // Get file changes const filesResult = await this.gitExecutor.executeGitCommand('diff', [ '--name-only', compareWith, 'HEAD' ], params.projectPath); const changedFiles = filesResult.stdout .split('\n') .filter(line => line.trim()); const executionTime = Date.now() - startTime; return { success: true, data: { compareWith, compareType, commits, changedFiles, diffStats: this.parseDiffStats(diffResult.stdout), summary: { commitsAhead: commits.length, filesChanged: changedFiles.length } }, metadata: { operation: params.action, timestamp: new Date().toISOString(), executionTime } }; } catch (error) { return OperationErrorHandler.createToolError( 'COMPARE_ERROR', `Failed to compare: ${error instanceof Error ? error.message : 'Unknown error'}`, params.action, { compareWith: params.compareWith, compareType: params.compareType }, ['Check target reference and repository status'] ); } } // Helper methods private async updateDependencies(projectPath: string): Promise<void> { // Update package.json dependencies const packageJsonPath = `${projectPath}/package.json`; const fs = require('fs'); if (fs.existsSync(packageJsonPath)) { // This is a placeholder - in a real implementation, you would: // 1. Parse package.json // 2. Check for outdated dependencies // 3. Update to latest versions // 4. Run npm update or similar this.logger.info('Updating dependencies'); } } private async updateCode(projectPath: string): Promise<void> { // Update code files based on patterns or rules this.logger.info('Updating code files'); } private async updateDocs(projectPath: string): Promise<void> { // Update documentation files this.logger.info('Updating documentation'); } private async updateConfig(projectPath: string): Promise<void> { // Update configuration files this.logger.info('Updating configuration'); } private async updateAll(projectPath: string): Promise<void> { await this.updateDependencies(projectPath); await this.updateCode(projectPath); await this.updateDocs(projectPath); await this.updateConfig(projectPath); } private async getCurrentCommitHash(projectPath: string): Promise<string> { const result = await this.gitExecutor.executeGitCommand('rev-parse', ['HEAD'], projectPath); return result.success ? result.stdout.trim() : ''; } private async getCurrentVersion(projectPath: string): Promise<string> { const packageJsonPath = `${projectPath}/package.json`; const fs = require('fs'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); return packageJson.version || '1.0.0'; } return '1.0.0'; } private async getLastTag(projectPath: string): Promise<string | null> { const result = await this.gitExecutor.executeGitCommand('describe', ['--tags', '--abbrev=0'], projectPath); return result.success ? result.stdout.trim() : null; } private async getChangedFiles(sinceRef: string, projectPath: string): Promise<string[]> { const result = await this.gitExecutor.executeGitCommand('diff', [ '--name-only', sinceRef, 'HEAD' ], projectPath); return result.success ? result.stdout.split('\n').filter(line => line.trim()) : []; } private parseCommitType(message: string): string { const match = message.match(/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:/); return match ? match[1] : 'other'; } private determineReleaseType(commits: any[]): string { if (commits.some(c => c.type === 'breaking' || c.message.includes('BREAKING CHANGE'))) { return 'breaking'; } if (commits.some(c => c.type === 'feat')) { return 'feature'; } if (commits.some(c => c.type === 'fix')) { return 'fix'; } return 'chore'; } private generateReleaseDescription(commits: any[]): string { const types = commits.reduce((acc, commit) => { acc[commit.type] = (acc[commit.type] || 0) + 1; return acc; }, {} as any); const descriptions = []; if (types.feat) descriptions.push(`${types.feat} new feature${types.feat > 1 ? 's' : ''}`); if (types.fix) descriptions.push(`${types.fix} bug fix${types.fix > 1 ? 'es' : ''}`); if (types.docs) descriptions.push(`${types.docs} documentation update${types.docs > 1 ? 's' : ''}`); if (types.refactor) descriptions.push(`${types.refactor} refactoring${types.refactor > 1 ? 's' : ''}`); return descriptions.length > 0 ? descriptions.join(', ') : 'Various improvements and fixes'; } private generateChangelogContent(entry: ChangelogEntry, groupByType: boolean): string { const date = entry.date; const version = entry.version; const description = entry.description; let content = `## [${version}] - ${date}\n\n`; content += `### ${this.capitalizeFirst(entry.type)}\n`; content += `${description}\n\n`; if (entry.commits.length > 0) { content += `#### Commits\n`; entry.commits.forEach(commit => { content += `- ${commit}\n`; }); content += '\n'; } if (entry.files.length > 0) { content += `#### Files Changed\n`; entry.files.forEach(file => { content += `- ${file}\n`; }); content += '\n'; } return content; } private mergeChangelogContent(newContent: string, existingContent: string): string { if (!existingContent) { return `# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n${newContent}`; } const lines = existingContent.split('\n'); const insertIndex = lines.findIndex(line => line.startsWith('## [')); if (insertIndex === -1) { return `${existingContent}\n\n${newContent}`; } lines.splice(insertIndex, 0, newContent); return lines.join('\n'); } private async trackFile(filePath: string, projectPath: string): Promise<any> { const result = await this.gitExecutor.executeGitCommand('status', ['--porcelain', filePath], projectPath); return { file: filePath, status: result.success ? result.stdout.trim() : 'unknown', exists: require('fs').existsSync(`${projectPath}/${filePath}`) }; } private async trackPattern(pattern: string, projectPath: string): Promise<string[]> { const result = await this.gitExecutor.executeGitCommand('ls-files', [pattern], projectPath); return result.success ? result.stdout.split('\n').filter(line => line.trim()) : []; } private async trackAllChanges(projectPath: string): Promise<string[]> { const result = await this.gitExecutor.executeGitCommand('status', ['--porcelain'], projectPath); return result.success ? result.stdout.split('\n').filter(line => line.trim()).map(line => line.substring(3).trim()) : []; } private formatHistoryMarkdown(commits: any[], history: UpdateHistory[]): string { let content = '# Update History\n\n'; content += '## Recent Commits\n\n'; commits.slice(0, 10).forEach(commit => { content += `- **${commit.hash}** - ${commit.message} (${commit.author}, ${commit.date})\n`; }); content += '\n## Update Operations\n\n'; history.slice(-10).forEach(entry => { content += `- **${entry.timestamp}** - ${entry.action}: ${entry.details}\n`; }); return content; } private formatHistoryText(commits: any[], history: UpdateHistory[]): string { let content = 'UPDATE HISTORY\n'; content += '==============\n\n'; content += 'RECENT COMMITS:\n'; commits.slice(0, 10).forEach(commit => { content += `${commit.hash} - ${commit.message} (${commit.author}, ${commit.date})\n`; }); content += '\nUPDATE OPERATIONS:\n'; history.slice(-10).forEach(entry => { content += `${entry.timestamp} - ${entry.action}: ${entry.details}\n`; }); return content; } private parseDiffStats(diffOutput: string): any { const lines = diffOutput.split('\n'); let insertions = 0; let deletions = 0; let files = 0; lines.forEach(line => { if (line.startsWith('+++') || line.startsWith('---')) { if (line.includes('b/')) files++; } else if (line.startsWith('+') && !line.startsWith('+++')) { insertions++; } else if (line.startsWith('-') && !line.startsWith('---')) { deletions++; } }); return { insertions, deletions, files }; } private capitalizeFirst(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } }

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