Skip to main content
Glama
git.ts5.05 kB
import { z } from 'zod'; import { exec } from 'child_process'; import { promisify } from 'util'; import * as path from 'path'; const execAsync = promisify(exec); /** * Git workflow tools */ // Tool schemas export const GitCreateBranchSchema = z.object({ branchName: z.string().describe('Name of the branch to create'), fromBranch: z.string().optional().describe('Base branch to create from (default: current branch)'), workspacePath: z.string().optional().describe('Workspace directory path'), }); /** * Create a working branch for a feature */ export async function gitCreateBranch(params: z.infer<typeof GitCreateBranchSchema>) { const { branchName, fromBranch, workspacePath = process.cwd() } = params; // Validate workspace path const resolvedPath = path.resolve(workspacePath); await validateWorkspacePath(resolvedPath); try { // Validate branch name (basic validation) if (!/^[a-zA-Z0-9/_-]+$/.test(branchName)) { throw new Error('Invalid branch name. Use only alphanumeric characters, hyphens, underscores, and slashes.'); } // Check if git repo exists try { await execAsync('git rev-parse --git-dir', { cwd: resolvedPath }); } catch { return { success: false, message: 'Not a git repository', suggestion: 'Initialize a git repository first with: git init', details: { workspacePath: resolvedPath, nextSteps: [ 'Run: git init', 'Configure git user: git config user.email "you@example.com"', 'Configure git name: git config user.name "Your Name"', 'Make initial commit: git add . && git commit -m "Initial commit"' ] } }; } // Get current branch const { stdout: currentBranch } = await execAsync('git branch --show-current', { cwd: resolvedPath, }); // Check for uncommitted changes const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd: resolvedPath, }); if (statusOutput.trim()) { const files = statusOutput.trim().split('\n'); const modifiedCount = files.filter(f => f.startsWith(' M')).length; const untrackedCount = files.filter(f => f.startsWith('??')).length; const stagedCount = files.filter(f => f.match(/^[AM]/)).length; return { success: false, message: 'Cannot create branch: uncommitted changes detected', details: { uncommittedFiles: files.length, modified: modifiedCount, untracked: untrackedCount, staged: stagedCount, files: files.slice(0, 5).map(f => f.substring(3)), // Show first 5 files nextSteps: [ 'Option 1: Commit changes: git add . && git commit -m "Your message"', 'Option 2: Stash changes: git stash', 'Option 3: Discard changes: git reset --hard (WARNING: loses changes)' ] }, }; } // Create the branch let command: string; if (fromBranch) { // First ensure we have the base branch try { await execAsync(`git checkout ${fromBranch}`, { cwd: resolvedPath }); } catch { throw new Error(`Base branch '${fromBranch}' not found`); } command = `git checkout -b ${branchName}`; } else { command = `git checkout -b ${branchName}`; } const { stdout } = await execAsync(command, { cwd: resolvedPath, timeout: 10000, }); // Verify branch was created const { stdout: newBranch } = await execAsync('git branch --show-current', { cwd: resolvedPath, }); if (newBranch.trim() !== branchName) { throw new Error('Branch creation verification failed'); } return { success: true, message: `Created and switched to branch: ${branchName}`, details: { branchName, previousBranch: currentBranch.trim(), fromBranch: fromBranch || currentBranch.trim(), output: stdout.trim(), }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; // Try to provide helpful error messages if (errorMessage.includes('already exists')) { return { success: false, message: `Branch '${branchName}' already exists`, suggestion: 'Use a different branch name or switch to existing branch', }; } throw new Error(`Failed to create branch: ${errorMessage}`); } } /** * Validate workspace path for security */ async function validateWorkspacePath(workspacePath: string): Promise<void> { // Prevent access to system directories const restrictedPaths = ['/etc', '/usr', '/bin', '/sbin', '/var', '/tmp']; const normalizedPath = path.normalize(workspacePath).toLowerCase(); for (const restricted of restrictedPaths) { if (normalizedPath.startsWith(restricted)) { throw new Error(`Access to system directory ${restricted} is not allowed`); } } }

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/flight505/MCP_DinCoder'

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