diff --git a/main.js b/main.js index 66c8c87..eb58c1a 100644 --- a/main.js +++ b/main.js @@ -1,3 +1,239 @@ +const { app, BrowserWindow, ipcMain, dialog, Menu, shell, clipboard } = require('electron'); +app.name = 'Auto-Git'; +const { exec } = require('child_process'); +const { spawn } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const Store = require('electron-store'); +const simpleGit = require('simple-git'); +//const fetch = require('node-fetch'); +const chokidar = require('chokidar'); + +const store = new Store({ + defaults: { + folders: [], + selected: null, + skymode: true, + skipGitPrompt: true, + intelligentCommitThreshold: 100 + } +}); + +// Map zum Speichern der Watcher pro Ordner +const repoWatchers = new Map(); + + + +// Debug Helper +function debug(msg) { + console.log(`[DEBUG ${new Date().toISOString()}] ${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 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 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