/**
* Git Workflow Tool
*
* Core Git workflow tool for local and remote Git operations.
* Supports init, status, commit, sync, backup, create, list, get, update, delete, fork, and search operations.
* Provides comprehensive Git repository management with both local and remote provider support (GitHub/Gitea).
* In universal mode (GIT_MCP_MODE=universal), automatically executes on both GitHub and Gitea providers.
*/
import { ParameterValidator, ToolParams } from '../utils/parameter-validator.js';
import { OperationErrorHandler, ToolResult } from '../utils/operation-error-handler.js';
import { ProviderOperationHandler, OperationResult } from '../providers/provider-operation-handler.js';
import { ProviderConfig, ProviderOperation } from '../providers/types.js';
import { configManager } from '../config.js';
import { GitCommandExecutor } from '../utils/git-command-executor.js';
export interface GitWorkflowParams extends ToolParams {
action: 'init' | 'status' | 'commit' | 'sync' | 'backup' | 'push' | 'pull' | 'create' | 'list' | 'get' | 'update' | 'delete' | 'fork' | 'search';
// Local operation parameters
message?: string; // For commit
files?: string[]; // For commit (specific files)
bare?: boolean; // For init
backupPath?: string; // For backup
// Remote operation parameters
name?: string; // For create
description?: string; // For create/update
private?: boolean; // For create
query?: string; // For search
repo?: string; // For get/update/delete/fork
// Sync parameters
remote?: string; // For sync (default: origin)
branch?: string; // For sync
force?: boolean; // For sync
// Push/Pull parameters
pushRemote?: string; // For push (default: origin)
pushBranch?: string; // For push (default: current branch)
pullRemote?: string; // For pull (default: origin)
pullBranch?: string; // For pull (default: current branch)
}
export class GitWorkflowTool {
private gitExecutor: GitCommandExecutor;
private providerHandler?: ProviderOperationHandler;
constructor(providerConfig?: ProviderConfig) {
this.gitExecutor = new GitCommandExecutor();
if (providerConfig) {
this.providerHandler = new ProviderOperationHandler(providerConfig);
}
}
/**
* Execute git-workflow operation
*/
async execute(params: GitWorkflowParams): Promise<ToolResult> {
const startTime = Date.now();
try {
// Validate basic parameters
const validation = ParameterValidator.validateToolParams('git-workflow', params);
if (!validation.isValid) {
return OperationErrorHandler.createToolError(
'VALIDATION_ERROR',
`Parameter validation failed: ${validation.errors.join(', ')}`,
params.action,
{ validationErrors: validation.errors },
validation.suggestions
);
}
// Validate operation-specific parameters
const operationValidation = ParameterValidator.validateOperationParams('git-workflow', params.action, params);
if (!operationValidation.isValid) {
return OperationErrorHandler.createToolError(
'VALIDATION_ERROR',
`Operation validation failed: ${operationValidation.errors.join(', ')}`,
params.action,
{ validationErrors: operationValidation.errors },
operationValidation.suggestions
);
}
// Route to appropriate handler
const isRemoteOperation = this.isRemoteOperation(params.action);
if (isRemoteOperation) {
return await this.executeRemoteOperation(params, startTime);
} else {
return await this.executeLocalOperation(params, startTime);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'EXECUTION_ERROR',
`Failed to execute ${params.action}: ${errorMessage}`,
params.action,
{ error: errorMessage },
['Check the error details and try again']
);
}
}
/**
* Check if operation is remote
*/
private isRemoteOperation(action: string): boolean {
// Remote operations require provider
const remoteOperations = ['create', 'list', 'get', 'update', 'delete', 'fork', 'search'];
return remoteOperations.includes(action);
}
/**
* Execute remote repository operations
*/
private async executeRemoteOperation(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
// Check provider configuration
if (!this.providerHandler) {
return OperationErrorHandler.createToolError(
'PROVIDER_NOT_CONFIGURED',
'Provider handler is not configured for remote operations',
params.action,
{},
['Configure GitHub or Gitea provider to use remote operations']
);
}
if (!params.provider) {
if (configManager.isUniversalMode()) {
params.provider = 'both';
console.error(`[Universal Mode] Auto-applying both providers for ${params.action}`);
} else {
return OperationErrorHandler.createToolError(
'PROVIDER_REQUIRED',
'Provider parameter is required for remote operations',
params.action,
{},
['Specify provider as: github, gitea, or both']
);
}
}
return await this.executeRepositoryOperation(params, startTime);
}
/**
* Execute local Git operations
*/
private async executeLocalOperation(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
try {
switch (params.action) {
case 'init':
return await this.executeInit(params, startTime);
case 'status':
return await this.executeStatus(params, startTime);
case 'commit':
return await this.executeCommit(params, startTime);
case 'sync':
return await this.executeSync(params, startTime);
case 'backup':
return await this.executeBackup(params, startTime);
case 'push':
return await this.executePush(params, startTime);
case 'pull':
return await this.executePull(params, startTime);
default:
return OperationErrorHandler.createToolError(
'UNSUPPORTED_OPERATION',
`Local operation '${params.action}' is not supported`,
params.action,
{},
['Use supported local operations: init, status, commit, sync, backup, push, pull']
);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'LOCAL_OPERATION_ERROR',
`Local operation failed: ${errorMessage}`,
params.action,
{ error: errorMessage },
['Check Git installation and repository state']
);
}
}
/**
* Execute repository operation through provider
*/
private async executeRepositoryOperation(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
const operation: ProviderOperation = {
provider: params.provider!,
operation: this.mapActionToProviderOperation(params.action),
parameters: this.extractRepositoryParameters(params),
requiresAuth: true,
isRemoteOperation: true
};
try {
const result = await this.providerHandler!.executeOperation(operation);
return {
success: result.success,
data: result.partialFailure ? result : result.results[0]?.data,
error: result.success ? undefined : {
code: result.errors[0]?.error?.code || 'REPO_OPERATION_ERROR',
message: result.errors[0]?.error?.message || 'Repository operation failed',
details: result.errors,
suggestions: this.getOperationSuggestions(params.action)
},
metadata: {
provider: params.provider,
operation: params.action,
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'REPO_OPERATION_ERROR',
`Repository operation failed: ${errorMessage}`,
params.action,
{ error: errorMessage },
['Check provider configuration and network connectivity']
);
}
}
/**
* Execute Git init
*/
private async executeInit(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
const initCommand = params.bare ? ['init', '--bare'] : ['init'];
const result = await this.gitExecutor.executeGitCommand('init', initCommand, params.projectPath);
if (result.success) {
return {
success: true,
data: { message: 'Git repository initialized successfully' },
metadata: {
operation: 'init',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
return OperationErrorHandler.createToolError(
'INIT_FAILED',
`Git init failed: ${result.stderr}`,
'init',
{ stderr: result.stderr },
['Check if directory exists and is writable', 'Verify Git installation']
);
}
}
/**
* Execute Git status
*/
private async executeStatus(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
const result = await this.gitExecutor.executeGitCommand('status', ['--porcelain'], params.projectPath);
if (result.success) {
const hasChanges = result.stdout.trim().length > 0;
return {
success: true,
data: {
hasChanges,
changes: hasChanges ? result.stdout.trim().split('\n') : [],
message: hasChanges ? 'Working directory has uncommitted changes' : 'Working directory is clean'
},
metadata: {
operation: 'status',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
return OperationErrorHandler.createToolError(
'STATUS_FAILED',
`Git status failed: ${result.stderr}`,
'status',
{ stderr: result.stderr },
['Ensure this is a Git repository', 'Check Git installation']
);
}
}
/**
* Execute Git commit
*/
private async executeCommit(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
if (!params.message) {
return OperationErrorHandler.createToolError(
'COMMIT_MESSAGE_REQUIRED',
'Commit message is required',
'commit',
{},
['Provide a commit message with the message parameter']
);
}
const commitArgs = ['-m', params.message];
if (params.files && params.files.length > 0) {
// Add specific files first
const addResult = await this.gitExecutor.executeGitCommand('add', params.files, params.projectPath);
if (!addResult.success) {
return OperationErrorHandler.createToolError(
'ADD_FAILED',
`Failed to add files: ${addResult.stderr}`,
'commit',
{ stderr: addResult.stderr },
['Check if files exist', 'Verify file paths are correct']
);
}
} else {
// Add all changes
const addResult = await this.gitExecutor.executeGitCommand('add', ['.'], params.projectPath);
if (!addResult.success) {
return OperationErrorHandler.createToolError(
'ADD_FAILED',
`Failed to add files: ${addResult.stderr}`,
'commit',
{ stderr: addResult.stderr },
['Check working directory status']
);
}
}
const result = await this.gitExecutor.executeGitCommand('commit', commitArgs, params.projectPath);
if (result.success) {
return {
success: true,
data: { message: 'Changes committed successfully' },
metadata: {
operation: 'commit',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
return OperationErrorHandler.createToolError(
'COMMIT_FAILED',
`Git commit failed: ${result.stderr}`,
'commit',
{ stderr: result.stderr },
['Check if there are changes to commit', 'Ensure commit message is provided']
);
}
}
/**
* Execute Git sync (pull + push)
*/
private async executeSync(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
const remote = params.remote || 'origin';
const branch = params.branch || await this.getCurrentBranch(params.projectPath) || 'main';
// Pull first
const pullResult = await this.gitExecutor.executeGitCommand('pull', [remote, branch], params.projectPath);
if (!pullResult.success) {
return OperationErrorHandler.createToolError(
'PULL_FAILED',
`Git pull failed: ${pullResult.stderr}`,
'sync',
{ stderr: pullResult.stderr },
['Check remote configuration', 'Resolve merge conflicts if any']
);
}
// Push
const pushArgs = params.force ? ['--force'] : [];
pushArgs.push(remote, branch);
const pushResult = await this.gitExecutor.executeGitCommand('push', pushArgs, params.projectPath);
if (pushResult.success) {
return {
success: true,
data: { message: 'Repository synchronized successfully' },
metadata: {
operation: 'sync',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
return OperationErrorHandler.createToolError(
'PUSH_FAILED',
`Git push failed: ${pushResult.stderr}`,
'sync',
{ stderr: pushResult.stderr },
['Check remote configuration', 'Verify you have push permissions']
);
}
}
/**
* Execute Git push
*/
private async executePush(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
const remote = params.pushRemote || 'origin';
const branch = params.pushBranch || await this.getCurrentBranch(params.projectPath) || 'main';
const pushArgs = [remote, branch];
const result = await this.gitExecutor.executeGitCommand('push', pushArgs, params.projectPath);
if (result.success) {
return {
success: true,
data: { message: 'Changes pushed successfully' },
metadata: {
operation: 'push',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
return OperationErrorHandler.createToolError(
'PUSH_FAILED',
`Git push failed: ${result.stderr}`,
'push',
{ stderr: result.stderr },
['Check remote configuration', 'Verify you have push permissions']
);
}
}
/**
* Execute Git pull
*/
private async executePull(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
const remote = params.pullRemote || 'origin';
const branch = params.pullBranch || await this.getCurrentBranch(params.projectPath) || 'main';
const result = await this.gitExecutor.executeGitCommand('pull', [remote, branch], params.projectPath);
if (result.success) {
return {
success: true,
data: { message: 'Changes pulled successfully' },
metadata: {
operation: 'pull',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
return OperationErrorHandler.createToolError(
'PULL_FAILED',
`Git pull failed: ${result.stderr}`,
'pull',
{ stderr: result.stderr },
['Check remote configuration', 'Resolve merge conflicts if any']
);
}
}
/**
* Execute backup
*/
private async executeBackup(params: GitWorkflowParams, startTime: number): Promise<ToolResult> {
const backupPath = params.backupPath || `backup-${Date.now()}.tar.gz`;
const result = await this.gitExecutor.executeGitCommand('archive', ['--format=tar', '--output', backupPath, 'HEAD'], params.projectPath);
if (result.success) {
return {
success: true,
data: { backupPath, message: 'Repository backed up successfully' },
metadata: {
operation: 'backup',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
return OperationErrorHandler.createToolError(
'BACKUP_FAILED',
`Backup failed: ${result.stderr}`,
'backup',
{ stderr: result.stderr },
['Check write permissions', 'Verify Git repository integrity']
);
}
}
/**
* Extract parameters for repository operations
*/
private extractRepositoryParameters(params: GitWorkflowParams): Record<string, any> {
const repoParams: Record<string, any> = {
projectPath: params.projectPath
};
// Auto-detect repo if not provided
if (params.repo) repoParams.repo = params.repo;
// Operation-specific parameters
switch (params.action) {
case 'create':
if (params.name) repoParams.name = params.name;
if (params.description) repoParams.description = params.description;
if (params.private !== undefined) repoParams.private = params.private;
break;
case 'list':
// No additional parameters needed
break;
case 'get':
case 'update':
case 'delete':
case 'fork':
// repo parameter already handled above
if (params.description) repoParams.description = params.description;
break;
case 'search':
if (params.query) repoParams.query = params.query;
break;
}
return repoParams;
}
/**
* Map git-workflow actions to provider operations
*/
private mapActionToProviderOperation(action: string): string {
const actionMap: Record<string, string> = {
'create': 'repo-create',
'list': 'repo-list',
'get': 'repo-get',
'update': 'repo-update',
'delete': 'repo-delete',
'fork': 'repo-fork',
'search': 'repo-search'
};
return actionMap[action] || action;
}
/**
* Get current branch name
*/
private async getCurrentBranch(projectPath: string): Promise<string | null> {
try {
const result = await this.gitExecutor.executeGitCommand('branch', ['--show-current'], projectPath);
if (result.success && result.stdout.trim()) {
return result.stdout.trim();
}
return null;
} catch (error) {
return null;
}
}
/**
* Get operation-specific suggestions
*/
private getOperationSuggestions(action: string): string[] {
const suggestions: Record<string, string[]> = {
'create': [
'Provide a unique repository name',
'Check provider permissions for repository creation',
'Verify repository name format'
],
'list': [
'Check provider access permissions',
'Verify provider configuration'
],
'get': [
'Verify repository exists',
'Check repository access permissions',
'Ensure correct repository name'
],
'update': [
'Verify repository exists and you have admin access',
'Check what fields you can update',
'Ensure repository name uniqueness if changing name'
],
'delete': [
'Verify repository exists and you have delete permissions',
'Ensure you really want to delete the repository',
'Consider creating a backup first'
],
'fork': [
'Verify source repository exists and is accessible',
'Check fork permissions on the source repository',
'Ensure you don\'t already have a fork'
],
'search': [
'Provide a search query',
'Check search syntax for the provider',
'Try different search terms'
]
};
return suggestions[action] || ['Check provider configuration and try again'];
}
/**
* Get tool schema for MCP registration
*/
static getToolSchema() {
return {
name: 'git-workflow',
description: 'Core Git workflow tool for local and remote Git operations. Supports init, status, commit, sync, backup, create, list, get, update, delete, fork, and search operations. Provides comprehensive Git repository management with both local and remote provider support (GitHub/Gitea). In universal mode (GIT_MCP_MODE=universal), automatically executes on both GitHub and Gitea providers.',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['init', 'status', 'commit', 'sync', 'backup', 'push', 'pull', 'create', 'list', 'get', 'update', 'delete', 'fork', 'search'],
description: 'The Git operation to perform. Local operations: init, status, commit, sync, backup. Remote operations: create, list, get, update, delete, fork, search (require provider parameter).'
},
projectPath: {
type: 'string',
description: 'Absolute path to the project directory'
},
provider: {
type: 'string',
enum: ['github', 'gitea', 'both'],
description: 'Provider for remote operations (required for remote operations)'
},
message: {
type: 'string',
description: 'Commit message (required for commit action)'
},
files: {
type: 'array',
items: { type: 'string' },
description: 'Specific files to commit (default: all changes)'
},
bare: {
type: 'boolean',
description: 'Initialize as bare repository (for init action)'
},
backupPath: {
type: 'string',
description: 'Path for backup file (for backup action)'
},
name: {
type: 'string',
description: 'Repository name (for create action)'
},
description: {
type: 'string',
description: 'Repository description (for create/update actions)'
},
private: {
type: 'boolean',
description: 'Create private repository (for create action)'
},
query: {
type: 'string',
description: 'Search query (for search action)'
},
repo: {
type: 'string',
description: 'Repository name (for get/update/delete/fork actions)'
},
remote: {
type: 'string',
description: 'Remote name for sync (default: origin)'
},
branch: {
type: 'string',
description: 'Branch name for sync'
},
force: {
type: 'boolean',
description: 'Force operation (for sync action)'
},
pushRemote: {
type: 'string',
description: 'Remote name for push (default: origin)'
},
pushBranch: {
type: 'string',
description: 'Branch name for push (default: current branch)'
},
pullRemote: {
type: 'string',
description: 'Remote name for pull (default: origin)'
},
pullBranch: {
type: 'string',
description: 'Branch name for pull (default: current branch)'
}
},
required: ['action', 'projectPath']
}
};
}
}