@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
JavaScript
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;