diff --git a/main.js b/main.js index f4e5153..a43dadc 100644 --- a/main.js +++ b/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) { cleanRebaseDirs(repoPath);