UNPKG

@codejoy/terminal-pet

Version:

A virtual pet that lives in your terminal and grows with your coding activity

243 lines (207 loc) 6.54 kB
const simpleGit = require('simple-git'); const fs = require('fs'); const path = require('path'); class GitIntegration { constructor() { this.git = simpleGit(); } async isGitRepository() { try { await this.git.status(); return true; } catch (error) { return false; } } async getRecentCommits(limit = 10) { try { const log = await this.git.log({ maxCount: limit }); return log.all.map(commit => ({ hash: commit.hash, message: commit.message, date: commit.date, author: commit.author_name })); } catch (error) { console.error('Error getting commits:', error); return []; } } async getTodaysCommits() { try { const today = new Date(); today.setHours(0, 0, 0, 0); const todayString = today.toISOString().split('T')[0]; const log = await this.git.log({ since: todayString, until: 'now' }); return log.all.length; } catch (error) { console.error('Error getting today\'s commits:', error); return 0; } } async getCommitStreak() { try { const commits = await this.getRecentCommits(100); if (commits.length === 0) return 0; const commitDates = commits.map(commit => { const date = new Date(commit.date); date.setHours(0, 0, 0, 0); return date.getTime(); }); // Remove duplicates and sort const uniqueDates = [...new Set(commitDates)].sort((a, b) => b - a); let streak = 0; const today = new Date(); today.setHours(0, 0, 0, 0); let currentDate = today.getTime(); // Check if there's a commit today or yesterday const dayMs = 24 * 60 * 60 * 1000; if (uniqueDates[0] === currentDate || uniqueDates[0] === currentDate - dayMs) { for (let i = 0; i < uniqueDates.length; i++) { if (uniqueDates[i] === currentDate - (streak * dayMs)) { streak++; } else { break; } } } return streak; } catch (error) { console.error('Error calculating streak:', error); return 0; } } async analyzeCommitActivity() { try { const commits = await this.getRecentCommits(50); const analysis = { totalCommits: commits.length, bugFixes: 0, features: 0, refactors: 0, averageMessageLength: 0, commitFrequency: {} }; let totalMessageLength = 0; commits.forEach(commit => { const message = commit.message.toLowerCase(); totalMessageLength += commit.message.length; // Categorize commits if (message.includes('fix') || message.includes('bug')) { analysis.bugFixes++; } if (message.includes('feat') || message.includes('add')) { analysis.features++; } if (message.includes('refactor') || message.includes('clean')) { analysis.refactors++; } // Track commit frequency by day const date = new Date(commit.date).toDateString(); analysis.commitFrequency[date] = (analysis.commitFrequency[date] || 0) + 1; }); analysis.averageMessageLength = commits.length > 0 ? Math.round(totalMessageLength / commits.length) : 0; return analysis; } catch (error) { console.error('Error analyzing commit activity:', error); return null; } } async setupGitHooks(petInstance) { try { const gitDir = await this.git.revparse(['--git-dir']); const hooksDir = path.join(gitDir, 'hooks'); const postCommitHook = path.join(hooksDir, 'post-commit'); // Create hooks directory if it doesn't exist if (!fs.existsSync(hooksDir)) { fs.mkdirSync(hooksDir, { recursive: true }); } // Create post-commit hook const hookContent = `#!/bin/sh # Terminal Pet post-commit hook # This hook feeds your pet after each commit # Get the commit message COMMIT_MSG=$(git log -1 --pretty=%B) # Call the pet CLI to handle the commit if command -v pet >/dev/null 2>&1; then pet commit "$COMMIT_MSG" >/dev/null 2>&1 & fi `; fs.writeFileSync(postCommitHook, hookContent); fs.chmodSync(postCommitHook, '755'); return true; } catch (error) { console.error('Error setting up git hooks:', error); return false; } } async removeGitHooks() { try { const gitDir = await this.git.revparse(['--git-dir']); const postCommitHook = path.join(gitDir, 'hooks', 'post-commit'); if (fs.existsSync(postCommitHook)) { const content = fs.readFileSync(postCommitHook, 'utf8'); if (content.includes('Terminal Pet')) { fs.unlinkSync(postCommitHook); return true; } } return false; } catch (error) { console.error('Error removing git hooks:', error); return false; } } async getRepositoryStats() { try { const status = await this.git.status(); const branches = await this.git.branch(); const remotes = await this.git.getRemotes(true); return { branch: branches.current, ahead: status.ahead, behind: status.behind, staged: status.staged.length, modified: status.modified.length, untracked: status.not_added.length, remotes: remotes.length }; } catch (error) { console.error('Error getting repository stats:', error); return null; } } async estimateLinesOfCode() { try { // This is a simple estimation based on commit diffs const commits = await this.getRecentCommits(20); let totalLines = 0; for (const commit of commits) { try { const diff = await this.git.show([commit.hash, '--numstat']); const lines = diff.split('\n').filter(line => line.trim()); lines.forEach(line => { const parts = line.split('\t'); if (parts.length >= 2) { const added = parseInt(parts[0]) || 0; const deleted = parseInt(parts[1]) || 0; totalLines += added - deleted; } }); } catch (error) { // Skip this commit if there's an error continue; } } return Math.max(0, totalLines); } catch (error) { console.error('Error estimating lines of code:', error); return 0; } } } module.exports = GitIntegration;