352 lines
8.5 KiB
JavaScript
352 lines
8.5 KiB
JavaScript
// 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
|
|
}; |