// src/git/gitHandler.js (CommonJS) const path = require('path'); const fs = require('fs'); const simpleGit = require('simple-git'); function gitFor(folderPath) { return simpleGit(folderPath); } async function initRepo(folderPath) { const git = gitFor(folderPath); if (!fs.existsSync(path.join(folderPath, '.git'))) { await git.init(); } return true; } async function commitAndPush(folderPath, branch = null, message = 'Update from Git Manager GUI', progressCb = null) { const git = gitFor(folderPath); // Branch auto-detect: falls nicht angegeben oder 'HEAD', aktuellen Branch ermitteln if (!branch || branch === 'HEAD') { const summary = await git.branchLocal(); branch = summary.current || 'main'; } await git.add('./*'); try { await git.commit(message); } catch (e) { if (!/nothing to commit/i.test(String(e))) throw e; } const localBranches = (await git.branchLocal()).all; if (!localBranches.includes(branch)) { await git.checkoutLocalBranch(branch); } else { await git.checkout(branch); } if (progressCb) progressCb(30); // push -u origin branch await git.push(['-u', 'origin', branch]); if (progressCb) progressCb(100); return true; } async function getBranches(folderPath) { const git = gitFor(folderPath); try { // Alle Branches: lokal + remote (origin/main, origin/master usw.) const summary = await git.branch(['-a']); const all = summary.all .map(b => b.replace(/^remotes\/origin\//, '').trim()) .filter(b => !b.startsWith('HEAD')) .filter((b, i, arr) => arr.indexOf(b) === i); // deduplizieren return all; } catch (e) { // Fallback: nur lokale Branches const local = await git.branchLocal(); return local.all; } } async function getCommitLogs(folderPath, count = 50) { const git = gitFor(folderPath); const log = await git.log({ n: count }); return log.all.map(c => `${c.hash.substring(0,7)} - ${c.message}`); } /* ================================ EXTENDED COMMIT HISTORY FUNCTIONS ================================ */ /** * Get detailed commit history with full information */ async function getDetailedCommitHistory(folderPath, options = {}) { const git = gitFor(folderPath); const logOptions = { maxCount: options.limit || 100, file: options.file || undefined }; if (options.author) { logOptions['--author'] = options.author; } if (options.since) { logOptions['--since'] = options.since; } if (options.until) { logOptions['--until'] = options.until; } if (options.grep) { logOptions['--grep'] = options.grep; } const log = await git.log(logOptions); return log.all.map(commit => ({ hash: commit.hash, shortHash: commit.hash.substring(0, 7), author: commit.author_name, authorEmail: commit.author_email, date: commit.date, message: commit.message, body: commit.body, refs: commit.refs || '', parentHashes: commit.parent || [] })); } /** * Get commit diff for a specific commit */ async function getCommitDiff(folderPath, commitHash) { const git = gitFor(folderPath); try { // Get diff from parent to this commit const diff = await git.diff([`${commitHash}~1`, commitHash]); return diff; } catch (error) { // If no parent (first commit), show diff from empty tree try { const diff = await git.diff(['--root', commitHash]); return diff; } catch (err) { console.error('Error getting diff:', err); return ''; } } } /** * Get file changes for a specific commit */ async function getCommitFileChanges(folderPath, commitHash) { const git = gitFor(folderPath); try { const diff = await git.diffSummary([`${commitHash}~1`, commitHash]); return { files: diff.files.map(file => ({ file: file.file, changes: file.changes, insertions: file.insertions, deletions: file.deletions, binary: file.binary || false })), insertions: diff.insertions, deletions: diff.deletions, changed: diff.changed }; } catch (error) { // First commit case try { const diff = await git.diffSummary(['--root', commitHash]); return { files: diff.files.map(file => ({ file: file.file, changes: file.changes, insertions: file.insertions, deletions: file.deletions, binary: file.binary || false })), insertions: diff.insertions, deletions: diff.deletions, changed: diff.changed }; } catch (err) { console.error('Error getting file changes:', err); return { files: [], insertions: 0, deletions: 0, changed: 0 }; } } } /** * Get detailed commit info including stats */ async function getCommitDetails(folderPath, commitHash) { const git = gitFor(folderPath); try { const log = await git.log({ from: commitHash, to: commitHash, maxCount: 1 }); if (log.all.length === 0) { throw new Error('Commit not found'); } const commit = log.all[0]; const fileChanges = await getCommitFileChanges(folderPath, commitHash); const diff = await getCommitDiff(folderPath, commitHash); return { hash: commit.hash, shortHash: commit.hash.substring(0, 7), author: commit.author_name, authorEmail: commit.author_email, date: commit.date, message: commit.message, body: commit.body, refs: commit.refs || '', parentHashes: commit.parent || [], fileChanges, diff }; } catch (error) { console.error('Error getting commit details:', error); throw error; } } /** * Get branch graph data for visualization */ async function getBranchGraph(folderPath, limit = 50) { const git = gitFor(folderPath); try { // Get all branches const branches = await git.branchLocal(); const allBranches = branches.all; // Get graph structure const log = await git.log({ maxCount: limit, '--all': null, '--graph': null, '--decorate': null, '--oneline': null }); return { branches: allBranches, current: branches.current, commits: log.all }; } catch (error) { console.error('Error getting branch graph:', error); return { branches: [], current: '', commits: [] }; } } /** * Search commits by message, author, or content */ async function searchCommits(folderPath, query, options = {}) { const git = gitFor(folderPath); const logOptions = { maxCount: options.limit || 100, '--all': null }; // Search in commit message if (options.searchMessage !== false) { logOptions['--grep'] = query; } // Search by author if (options.author) { logOptions['--author'] = options.author; } // Case insensitive if (options.caseInsensitive !== false) { logOptions['--regexp-ignore-case'] = null; } try { const log = await git.log(logOptions); return log.all.map(commit => ({ hash: commit.hash, shortHash: commit.hash.substring(0, 7), author: commit.author_name, authorEmail: commit.author_email, date: commit.date, message: commit.message, body: commit.body, refs: commit.refs || '' })); } catch (error) { console.error('Error searching commits:', error); return []; } } /** * Get commit statistics */ async function getCommitStats(folderPath, since = '1 month ago') { const git = gitFor(folderPath); try { const log = await git.log({ '--since': since, '--all': null }); const authorStats = {}; const dailyStats = {}; log.all.forEach(commit => { // Author stats const author = commit.author_name; if (!authorStats[author]) { authorStats[author] = { commits: 0, email: commit.author_email }; } authorStats[author].commits++; // Daily stats const date = new Date(commit.date).toISOString().split('T')[0]; if (!dailyStats[date]) { dailyStats[date] = 0; } dailyStats[date]++; }); return { totalCommits: log.all.length, authors: authorStats, daily: dailyStats }; } catch (error) { console.error('Error getting commit stats:', error); return { totalCommits: 0, authors: {}, daily: {} }; } } module.exports = { initRepo, commitAndPush, getBranches, getCommitLogs, getDetailedCommitHistory, getCommitDiff, getCommitFileChanges, getCommitDetails, getBranchGraph, searchCommits, getCommitStats };