UNPKG

@dharshansr/gitgenius

Version:

AI-powered commit message generator with enhanced features

185 lines 6.88 kB
import simpleGit from 'simple-git'; import chalk from 'chalk'; import { GitStateManager } from '../utils/GitStateManager.js'; import { ErrorHandler } from '../utils/ErrorHandler.js'; export class GitService { constructor() { this.git = simpleGit(); this.stateManager = new GitStateManager(); } async checkIsRepo() { return await this.git.checkIsRepo(); } async ensureGitRepo() { const isRepo = await this.checkIsRepo(); if (!isRepo) { throw new Error('Not in a git repository'); } } async getCurrentBranch() { try { const status = await this.git.status(); // Check for detached HEAD if (status.detached || !status.current) { const state = await this.stateManager.getState(); console.log(chalk.yellow('⚠ Warning: Detached HEAD state detected')); console.log(chalk.yellow(`Currently at commit: ${state.currentCommit}`)); return state.currentCommit; } return status.current || 'main'; } catch (error) { throw ErrorHandler.gitError(`Failed to get current branch: ${error instanceof Error ? error.message : String(error)}`, ['Ensure you are in a Git repository', 'Check Git configuration']); } } async getStatus() { try { return await this.git.status(); } catch (error) { throw ErrorHandler.gitError(`Failed to get status: ${error instanceof Error ? error.message : String(error)}`, ['Ensure you are in a Git repository', 'Check for repository corruption']); } } async getDiff(options = []) { return await this.git.diff(options); } async getStagedDiff() { try { // Check for conflicts before showing diff const state = await this.stateManager.getState(); if (state.hasConflicts) { console.log(chalk.yellow('⚠ Warning: Merge conflicts detected')); const hints = await this.stateManager.getConflictResolutionHints(); hints.forEach(hint => console.log(chalk.yellow(hint))); } return await this.git.diff(['--staged']); } catch (error) { throw ErrorHandler.gitError(`Failed to get staged diff: ${error instanceof Error ? error.message : String(error)}`, ['Check if there are staged changes: git status']); } } async getLastCommitDiff() { return await this.git.diff(['HEAD~1', 'HEAD']); } async getFileDiff(file) { return await this.git.diff([file]); } async commit(message, options) { try { // Validate environment before committing const validation = await this.stateManager.validateEnvironment(); if (!validation.valid) { console.log(chalk.yellow('⚠ Environment warnings:')); validation.errors.forEach(err => console.log(chalk.yellow(` • ${err}`))); } // Check for conflicts const state = await this.stateManager.getState(); if (state.hasConflicts) { throw ErrorHandler.gitError('Cannot commit: merge conflicts detected', await this.stateManager.getConflictResolutionHints()); } await this.git.commit(message, undefined, options); } catch (error) { if (error instanceof Error && error.message.includes('nothing to commit')) { throw ErrorHandler.gitError('Nothing to commit', ['Stage changes first: git add <file>', 'Check status: git status']); } throw error; } } async reset(options = []) { await this.git.reset(options); } async getLog(options) { return await this.git.log(options); } async getBranchLocal() { return await this.git.branchLocal(); } async getConfig(key) { return await this.git.getConfig(key); } async addConfig(key, value) { await this.git.addConfig(key, value); } // New methods for enhanced Git operations /** * Get Git state manager */ getStateManager() { return this.stateManager; } /** * Check workspace cleanliness before operations */ async ensureCleanWorkspace(allowStaged = false) { await this.stateManager.ensureCleanWorkspace(allowStaged); } /** * Check for detached HEAD and handle appropriately */ async checkDetachedHead() { return await this.stateManager.isDetachedHead(); } /** * Get worktree information */ async getWorktrees() { return await this.stateManager.getWorktrees(); } /** * Get submodule information */ async getSubmodules() { return await this.stateManager.getSubmodules(); } /** * Display current Git state */ async displayState() { await this.stateManager.displayState(); } calculateStats(logs) { const stats = { totalCommits: logs.total, authors: {}, commitTypes: {}, filesChanged: 0, linesAdded: 0, linesDeleted: 0 }; logs.all.forEach((commit) => { // Count by author if (commit.author_name) { stats.authors[commit.author_name] = (stats.authors[commit.author_name] || 0) + 1; } // Count by commit type (conventional commits) const typeMatch = commit.message.match(/^(\w+)(\(.+\))?:/); if (typeMatch) { const type = typeMatch[1]; stats.commitTypes[type] = (stats.commitTypes[type] || 0) + 1; } }); return stats; } displayStats(stats, days, author) { console.log(chalk.blue(`[STATS] Git Statistics (${days} days)${author ? ` for ${author}` : ''}`)); console.log(chalk.yellow(`[INFO] Total commits: ${stats.totalCommits}`)); if (Object.keys(stats.authors).length > 0) { console.log(chalk.blue('\n[STATS] Commits by author:')); Object.entries(stats.authors) .sort(([, a], [, b]) => b - a) .forEach(([author, count]) => { console.log(` ${chalk.white(author)}: ${chalk.green(count)}`); }); } if (Object.keys(stats.commitTypes).length > 0) { console.log(chalk.blue('\n[STATS] Commits by type:')); Object.entries(stats.commitTypes) .sort(([, a], [, b]) => b - a) .forEach(([type, count]) => { console.log(` ${chalk.white(type)}: ${chalk.green(count)}`); }); } } } //# sourceMappingURL=GitService.js.map