auto-git:
[change] main.js
This commit is contained in:
259
main.js
259
main.js
@@ -30,265 +30,6 @@ function debug(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt das BrowserWindow und lädt index.html.
|
|
||||||
* Gibt das Window-Objekt zurück.
|
|
||||||
*/
|
|
||||||
function createWindow() {
|
|
||||||
const win = new BrowserWindow({
|
|
||||||
width: 900,
|
|
||||||
height: 600,
|
|
||||||
minWidth: 600,
|
|
||||||
minHeight: 400,
|
|
||||||
title: 'Auto-Git',
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
|
||||||
contextIsolation: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
win.loadFile('index.html');
|
|
||||||
return win;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Settings-Fenster
|
|
||||||
let settingsWin;
|
|
||||||
function openSettings(win) {
|
|
||||||
if (settingsWin) {
|
|
||||||
settingsWin.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settingsWin = new BrowserWindow({
|
|
||||||
parent: win,
|
|
||||||
modal: true,
|
|
||||||
width: 400,
|
|
||||||
height: 300,
|
|
||||||
resizable: false,
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
|
||||||
contextIsolation: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
settingsWin.removeMenu();
|
|
||||||
settingsWin.loadFile('settings.html');
|
|
||||||
settingsWin.on('closed', () => settingsWin = null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Startet einen File-Watcher auf .git/refs/heads/master,
|
|
||||||
* sendet bei Änderungen 'repo-updated' an den Renderer.
|
|
||||||
*/
|
|
||||||
function watchRepo(folder, win) {
|
|
||||||
const gitHead = path.join(folder, '.git', 'refs', 'heads', 'master');
|
|
||||||
const watcher = chokidar.watch(gitHead, { ignoreInitial: true });
|
|
||||||
watcher.on('change', () => {
|
|
||||||
win.webContents.send('repo-updated', folder);
|
|
||||||
});
|
|
||||||
repoWatchers.set(folder, watcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiiert ein Git-Repo in `folder`, falls noch nicht vorhanden,
|
|
||||||
* und erzeugt einen Initial-Commit mit Timestamp.
|
|
||||||
*/
|
|
||||||
async function initGitRepo(folder) {
|
|
||||||
const git = simpleGit(folder);
|
|
||||||
const gitDir = path.join(folder, '.git');
|
|
||||||
if (!fs.existsSync(gitDir)) {
|
|
||||||
await git.init();
|
|
||||||
const message = `Initial commit (generated by auto-git)`;
|
|
||||||
const readmePath = path.join(folder, 'README.md');
|
|
||||||
fs.writeFileSync(readmePath, `# Projekt in ${path.basename(folder)}\n`);
|
|
||||||
await git.add('./*');
|
|
||||||
await git.commit(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Map für Monitoring-Watcher (nicht repoWatchers!)
|
|
||||||
const monitoringWatchers = new Map();
|
|
||||||
|
|
||||||
// Helper: Baut eine Commit-Message aus git.status()
|
|
||||||
function buildCommitMessageFromStatus(status, prefix = '[auto]') {
|
|
||||||
const changes = [];
|
|
||||||
status.not_added.forEach(f => changes.push(`[add] ${f}`));
|
|
||||||
status.created.forEach(f => changes.push(`[add] ${f}`));
|
|
||||||
status.modified.forEach(f => changes.push(`[change] ${f}`));
|
|
||||||
status.deleted.forEach(f => changes.push(`[unlink] ${f}`));
|
|
||||||
status.renamed.forEach(r => changes.push(`[rename] ${r.from} → ${r.to}`));
|
|
||||||
return prefix + '\n' + changes.map(l => ` ${l}`).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function startMonitoringWatcher(folderPath, win) {
|
|
||||||
if (monitoringWatchers.has(folderPath)) return;
|
|
||||||
const watcher = chokidar.watch(folderPath, {
|
|
||||||
ignored: /(^|[\/\\])\..|node_modules|\.git/,
|
|
||||||
ignoreInitial: true,
|
|
||||||
persistent: true,
|
|
||||||
depth: 99,
|
|
||||||
awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialer Commit
|
|
||||||
(async () => {
|
|
||||||
debug(`[MONITOR] Starte initialen Commit-Check für ${folderPath}`);
|
|
||||||
const git = simpleGit(folderPath);
|
|
||||||
const status = await git.status();
|
|
||||||
if (
|
|
||||||
status.not_added.length > 0 ||
|
|
||||||
status.created.length > 0 ||
|
|
||||||
status.modified.length > 0 ||
|
|
||||||
status.deleted.length > 0 ||
|
|
||||||
status.renamed.length > 0
|
|
||||||
) {
|
|
||||||
const msg = buildCommitMessageFromStatus(status, 'auto-git: ');
|
|
||||||
const did = await autoCommit(folderPath, msg);
|
|
||||||
if (did) {
|
|
||||||
win.webContents.send('repo-updated', folderPath);
|
|
||||||
debug(`[MONITOR] Initialer Auto-Commit für ${folderPath} durchgeführt:\n${msg}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Bei jedem Event → status neu holen, Message wie beim initialen Check bauen
|
|
||||||
watcher.on('all', async () => {
|
|
||||||
const git = simpleGit(folderPath);
|
|
||||||
const status = await git.status();
|
|
||||||
if (
|
|
||||||
status.not_added.length > 0 ||
|
|
||||||
status.created.length > 0 ||
|
|
||||||
status.modified.length > 0 ||
|
|
||||||
status.deleted.length > 0 ||
|
|
||||||
status.renamed.length > 0
|
|
||||||
) {
|
|
||||||
const msg = buildCommitMessageFromStatus(status, 'auto-git: ');
|
|
||||||
await autoCommit(folderPath, msg);
|
|
||||||
win.webContents.send('repo-updated', folderPath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
monitoringWatchers.set(folderPath, watcher);
|
|
||||||
debug(`[MONITOR] Watcher aktiv für ${folderPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopMonitoringWatcher(folderPath) {
|
|
||||||
const watcher = monitoringWatchers.get(folderPath);
|
|
||||||
if (watcher) {
|
|
||||||
watcher.close();
|
|
||||||
monitoringWatchers.delete(folderPath);
|
|
||||||
debug(`[MONITOR] Watcher gestoppt für ${folderPath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---- Rewrite Git Messages with LLM generated messages ----
|
|
||||||
|
|
||||||
|
|
||||||
// 1. Commits & Diffs für LLM sammeln
|
|
||||||
async function getCommitsForLLM(folderPath, hashes) {
|
|
||||||
const git = simpleGit(folderPath);
|
|
||||||
const commits = [];
|
|
||||||
for (const hash of hashes) {
|
|
||||||
const diff = await git.diff([`${hash}^!`]);
|
|
||||||
const msg = (await git.show(['-s', '--format=%B', hash])).trim();
|
|
||||||
commits.push({
|
|
||||||
hash,
|
|
||||||
message: msg,
|
|
||||||
diff
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return commits;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPrompt(folderPath, hashes) {
|
|
||||||
const commits = await getCommitsForLLM(folderPath, hashes);
|
|
||||||
|
|
||||||
if (commits.length === 1) {
|
|
||||||
// Only one commit: Prompt LLM for a message just for this diff.
|
|
||||||
const diff = commits[0].diff;
|
|
||||||
return `You're a professional programmer who writes git commit messages all day.
|
|
||||||
Generate a concise git commit message for these changes:
|
|
||||||
|
|
||||||
${diff}
|
|
||||||
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
Answer VERY BRIEFLY. Don't give any feedback on the code, just analyze what changed and write the git commit message. Keep it short! A commit message MUST NOT BE STRAIGHT TO THE POINT!
|
|
||||||
Also reply to my message, just give me the commit message.`;
|
|
||||||
} else if (commits.length > 1) {
|
|
||||||
// Multiple commits: Squash them, give all diffs as a big change.
|
|
||||||
const combinedDiffs = commits.map(c => c.diff).join('\n\n');
|
|
||||||
return `You're a professional programmer who writes git commit messages all day.
|
|
||||||
Analyze the following code changes (from multiple commits). To squash them into a single commit I need a concise git commit message from you describing the changes in a single sentence.
|
|
||||||
Here are the combined diffs:
|
|
||||||
${combinedDiffs}
|
|
||||||
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
Even if this might seem like a lot of code, I need you to answer VERY BRIEFLY. A git commit message to be precise is what I need from you, to protocol these changes.
|
|
||||||
Don't give any feedback on the code! Just analyze what changed and write the git commit message. Keep it short! A commit message MUST NOT BE STRAIGHT TO THE POINT!
|
|
||||||
Don't reply to my message, just give me the commit message.`;
|
|
||||||
} else {
|
|
||||||
throw new Error('No commits found for LLM prompt.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 3. LLM Streaming Call
|
|
||||||
async function streamLLMCommitMessages(prompt, onDataChunk) {
|
|
||||||
console.log(prompt);
|
|
||||||
const response = await fetch('http://localhost:11434/api/generate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'qwen2.5-coder:32b',
|
|
||||||
prompt: prompt,
|
|
||||||
stream: true
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.body) throw new Error('No stream returned');
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
let fullOutput = '';
|
|
||||||
let done = false;
|
|
||||||
while (!done) {
|
|
||||||
const { value, done: streamDone } = await reader.read();
|
|
||||||
done = streamDone;
|
|
||||||
if (value) {
|
|
||||||
const chunk = decoder.decode(value, { stream: true });
|
|
||||||
for (const line of chunk.split('\n')) {
|
|
||||||
if (!line.trim()) continue;
|
|
||||||
try {
|
|
||||||
const obj = JSON.parse(line);
|
|
||||||
if (obj.response) {
|
|
||||||
fullOutput += obj.response;
|
|
||||||
if (onDataChunk) onDataChunk(obj.response);
|
|
||||||
}
|
|
||||||
if (obj.done) break;
|
|
||||||
} catch (e) {
|
|
||||||
// ignore malformed chunk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fullOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanRebaseDirs(repoPath) {
|
|
||||||
const gitDir = path.join(repoPath, '.git');
|
|
||||||
const rebaseDirs = ['rebase-merge', 'rebase-apply'];
|
|
||||||
for (const dir of rebaseDirs) {
|
|
||||||
const fullPath = path.join(gitDir, dir);
|
|
||||||
if (fs.existsSync(fullPath)) {
|
|
||||||
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
||||||
console.log(`[AutoGit] Entfernt alte ${dir}-Direktory: ${fullPath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function squashCommitMessages(repoPath, commitMessage, hashes) {
|
async function squashCommitMessages(repoPath, commitMessage, hashes) {
|
||||||
cleanRebaseDirs(repoPath);
|
cleanRebaseDirs(repoPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user