const { app, BrowserWindow, ipcMain, dialog, Menu, shell, clipboard } = require('electron'); app.name = 'Auto-Git'; const { exec } = 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; } // 2. Prompts für LLM bauen async function generateLLMCommitMessages(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 `Generate a concise git commit message for these changes: ${diff} Don't give any feedback on the code, just analyze what changed and write the git commit message. Keep it short.`; } 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 `Analyze the following code changes (from multiple commits). We will squash them to a single commit and need a concise git commit message for that. Don't give any feedback on the code, just analyze what changed and write the git commit message. Keep it short. Here are the combined diffs: ${combinedDiffs}`; } else { throw new Error('No commits found for LLM prompt.'); } } // 3. LLM Streaming Call async function streamLLMCommitMessages(prompt, onDataChunk) { 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 }); if (onDataChunk) onDataChunk(chunk); fullOutput += chunk; } } return fullOutput.trim(); } // 4. JSON Output robust parsen function parseLLMCommitMessages(rawOutput) { let cleaned = rawOutput.trim(); cleaned = cleaned.replace(/^```(?:json)?|```$/gmi, ''); try { if (cleaned.trim().startsWith('[')) return JSON.parse(cleaned); if (cleaned.trim().startsWith('{')) { const obj = JSON.parse(cleaned); return Object.entries(obj).map(([commit, newMessage]) => ({ commit, newMessage })); } // Zeilenweise Objekte (fuzzy) if (cleaned.includes('\n')) { let lines = cleaned.split('\n').map(l => l.trim()).filter(Boolean); let objs = []; for (let line of lines) { try { let o = JSON.parse(line); objs.push(o); } catch {} } if (objs.length) return objs; } } catch (err) { // Fallback repair try { cleaned = cleaned.replace(/}\s*{/g, '},\n{'); if (!cleaned.startsWith('[')) cleaned = '[' + cleaned; if (!cleaned.endsWith(']')) cleaned = cleaned + ']'; return JSON.parse(cleaned); } catch (e) { throw new Error('Could not parse LLM output:\n' + rawOutput); } } throw new Error('Could not parse LLM output:\n' + rawOutput); } // 5. Rewrite-Logik (mit simple-git, wie oben!) async function rewriteCommitMessages(repoPath, message) { const git = simpleGit(repoPath); await git.raw(['commit', '--amend', '-m', message, '--no-edit', '--allow-empty']); } /*async function rewriteCommitMessages(repoPath, messageMap, hashesToRewrite) { const git = simpleGit(repoPath); let branchesWithHashes = new Set(); for (let hash of hashesToRewrite) { const branches = await git.branch(['--contains', hash]); Object.keys(branches.branches).forEach(branch => { branchesWithHashes.add(branch); }); } for (let branch of branchesWithHashes) { await git.checkout(branch); const log = await git.log(); const commitsInBranch = log.all.map(c => c.hash); const targetHashes = commitsInBranch.filter(hash => hashesToRewrite.includes(hash)); if (targetHashes.length === 0) continue; // Von ältestem zum neuesten for (let i = log.all.length - 1; i >= 0; i--) { const c = log.all[i]; if (hashesToRewrite.includes(c.hash)) { const newMsg = messageMap[c.hash] || c.message; await git.raw(['commit', '--amend', '-m', newMsg, '--no-edit', '--allow-empty']); } } // Kein Push! } } */ /** * Komplett-Workflow: Von Kandidaten bis Rewrite */ //async function runLLMCommitPipeline(folderPath, hashes, win) { async function runLLMCommitPipeline(folderPath, hashes) { // 1. Prompt bauen const prompt = await generateLLMCommitMessages(folderPath, hashes); // 2. LLM Call & Streaming Output für die Katze (optional) const llmOutput = await streamLLMCommitMessages(prompt, chunk => process.stdout.write(chunk)); //console.log('LLM Output:\n', llmOutput); // Nur ein einziges Mal // 3. Robust parsen //const commitList = parseLLMCommitMessages(llmOutput); // [{commit, newMessage}] - fuck it, maybe later something else // 4. Hash->Message Mapping //const messageMap = {}; //for (const entry of commitList) { // messageMap[entry.commit] = entry.newMessage; //} // 5. Rewrite //await rewriteCommitMessages(folderPath, messageMap, hashes); if(hashes.length == 1){ const git = simpleGit(folderPath); await git.raw(['commit', '--amend', '-m', message, '--no-edit', '--allow-empty']); } else if (hashes.length > 1) { await squashCommitMessages(folderPath, llmOutput, hashes); //WE NEED THIS FUNCTION, WRITE IT PLEASE } else { //should never be reached, right? } // Optional: Notification anzeigen //if (win && win.webContents) { // win.webContents.send('llm-commit-rewrite-finished', { changed: hashes.length }); //} } async function autoCommit(folderPath, message) { const git = simpleGit(folderPath); const status = await git.status(); if ( status.not_added.length === 0 && status.created.length === 0 && status.deleted.length === 0 && status.modified.length === 0 && status.renamed.length === 0 ) { debug('Auto-Commit: Keine Änderungen zum committen.'); return false; } let currentBranch = null; try { currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim(); debug(`[autoCommit] Aktueller Branch: ${currentBranch}`); } catch { debug('[autoCommit] HEAD ist detached.'); currentBranch = null; } if (!currentBranch || currentBranch === 'HEAD') { // === Erweiterte Logs === const headCommit = (await git.revparse(['HEAD'])).trim(); let masterCommit = null; let hasMaster = false; try { masterCommit = (await git.revparse(['refs/heads/master'])).trim(); hasMaster = true; } catch (e) { debug('[autoCommit] master branch existiert nicht.'); masterCommit = null; hasMaster = false; } debug(`[autoCommit] HEAD: ${headCommit}`); debug(`[autoCommit] master: ${masterCommit}`); if (hasMaster && headCommit === masterCommit) { // HEAD ist detached, zeigt aber exakt auf master-Tip → einfach auf master auschecken. await git.checkout('master'); debug('[autoCommit] HEAD war detached, zeigte aber exakt auf master – jetzt zurück auf master.'); // Nach dem Checkout nochmal aktuellen Branch loggen: currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim(); debug(`[autoCommit] Nach checkout: Aktueller Branch: ${currentBranch}`); // Jetzt **NICHT** weiter zur Umbenenn-Logik! } else if (hasMaster) { // HEAD ist detached, zeigt auf einen anderen Commit → backup master und neuen master-Branch const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupBranch = `backup-master-${timestamp}`; await git.branch(['-m', 'master', backupBranch]); debug(`[autoCommit] Alter master in ${backupBranch} umbenannt.`); await git.checkout(['-b', 'master']); debug('[autoCommit] Neuer master-Branch erstellt und ausgecheckt.'); currentBranch = 'master'; } else { // Kein master vorhanden (erstmalig) await git.checkout(['-b', 'master']); debug('[autoCommit] Kein master-Branch vorhanden, neuer master erstellt.'); currentBranch = 'master'; } } // --- Zeilenzählung --- let diffOutput = await git.diff(['--numstat']); // Zeilensumme berechnen: let changedLines = 0; for (let line of diffOutput.split('\n')) { const match = line.match(/^(\d+|\-)\s+(\d+|\-)\s+(.*)$/); if (match) { const added = match[1] === '-' ? 0 : parseInt(match[1], 10); const deleted = match[2] === '-' ? 0 : parseInt(match[2], 10); changedLines += added + deleted; } } // Folders aus Store holen let folders = store.get('folders') || []; let idx = folders.findIndex(f => f.path === folderPath); if (idx !== -1) { folders[idx].linesChanged = (folders[idx].linesChanged || 0) + changedLines; folders[idx].llmCandidates = folders[idx].llmCandidates || []; // ===> WICHTIG: Wir commiten ja jetzt, daher merken wir uns gleich die neue Commit-Hash // Vor git.commit: Merke alten HEAD const oldHead = (await git.revparse(['HEAD'])).trim(); // Stagen & Committen await git.add(['-A']); debug('[autoCommit] Alle Änderungen gestaged.'); await git.commit(message || '[auto]'); debug('[autoCommit] Commit erfolgreich erstellt.'); // Nach Commit: neuen HEAD ermitteln und in llmCandidates speichern const newHead = (await git.revparse(['HEAD'])).trim(); folders[idx].llmCandidates = folders[idx].llmCandidates || []; folders[idx].llmCandidates.push(newHead); // Threshold holen const threshold = store.get('intelligentCommitThreshold') || 10; if (folders[idx].linesChanged >= threshold) { debug('Congratulations! You changed enough lines of code :)'); // **Hier: LLM-Workflow starten** //await runLLMCommitPipeline(folderPath, folders[idx].llmCandidates, win); await runLLMCommitPipeline(folderPath, folders[idx].llmCandidates); // Reset danach: folders[idx].linesChanged = 0; folders[idx].llmCandidates = []; store.set('folders', folders); } // Folder-Objekt speichern store.set('folders', folders); } else { // Folder not found! (Debug) debug(`[autoCommit] Warning: Folder ${folderPath} not found in store`); } } app.whenReady().then(() => { const win = createWindow(); // Menüs const menu = Menu.buildFromTemplate([ { role: 'appMenu', submenu: [ { label: 'Settings', click: () => openSettings(win) }, { role: 'quit', label: 'Quit' } ] }, { role: 'editMenu' } // <-- hiermit aktivierst du Copy/Paste via Ctrl+C / Cmd+C ]); Menu.setApplicationMenu(menu); // 1) Beim Start bereits gespeicherte Ordner überwachen und monitoren const folders = store.get('folders') || []; folders.forEach(folderObj => { if (fs.existsSync(path.join(folderObj.path, '.git', 'refs', 'heads', 'master'))) { watchRepo(folderObj.path, win); } if (folderObj.monitoring) { startMonitoringWatcher(folderObj.path, win); } }); // 2) IPC-Handler ipcMain.handle('get-selected', () => { const folders = store.get('folders') || []; const selectedPath = store.get('selected'); return folders.find(f => f.path === selectedPath) || null; }); ipcMain.handle('set-selected', (_e, folderObjOrPath) => { // Akzeptiert sowohl String (legacy) als auch Objekt: const folderPath = typeof folderObjOrPath === 'string' ? folderObjOrPath : folderObjOrPath.path; store.set('selected', folderPath); const folders = store.get('folders') || []; return folders.find(f => f.path === folderPath) || null; }); // Liste aller Folders ipcMain.handle('get-folders', () => store.get('folders')); // Ordner hinzufügen: Open-Dialog, init, Store-Update, watchen, monitoren ipcMain.handle('add-folder', async () => { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openDirectory'] }); if (canceled || !filePaths[0]) { return store.get('folders'); } const newFolder = filePaths[0]; await initGitRepo(newFolder); let folders = store.get('folders') || []; let folderObj = folders.find(f => f.path === newFolder); if (!folderObj) { folderObj = { path: newFolder, monitoring: true, linesChanged: 0, llmCandidates: [] }; folders.push(folderObj); store.set('folders', folders); } store.set('selected', newFolder); watchRepo(newFolder, win); startMonitoringWatcher(newFolder, win); return store.get('folders'); }); /* ipcMain.handle('add-folder', async () => { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openDirectory'] }); if (canceled || !filePaths[0]) return store.get('folders'); const newFolder = filePaths[0]; // Repo initialisieren await initGitRepo(newFolder); // Im Store ablegen const current = store.get('folders'); if (!current.includes(newFolder)) { store.set('folders', [...current, newFolder]); } store.set('selected', newFolder); // und watchen watchRepo(newFolder, win); return store.get('folders'); }); */ // Ordner entfernen: Watcher schließen, Store-Update ipcMain.handle('remove-folder', (_e, folderObj) => { const folders = store.get('folders') || []; const updated = folders.filter(f => f.path !== folderObj.path); store.set('folders', updated); if (store.get('selected') === folderObj.path) store.set('selected', null); stopMonitoringWatcher(folderObj.path); const watcher = repoWatchers.get(folderObj.path); if (watcher) watcher.close(), repoWatchers.delete(folderObj.path); return updated; }); /* ipcMain.handle('get-selected', () => store.get('selected')); ipcMain.handle('set-selected', (_e, folderPath) => { store.set('selected', folderPath); return folderPath; }); */ /* ipcMain.handle('remove-folder', (_e, folder) => { const watcher = repoWatchers.get(folder); if (watcher) { watcher.close(); repoWatchers.delete(folder); } const updated = store.get('folders').filter(f => f !== folder); store.set('folders', updated); if (store.get('selected') === folder) { store.set('selected', null); } return updated; }); */ // Zähle Commits ipcMain.handle('get-commit-count', async (_e, folderObj) => { const git = simpleGit(folderObj.path); const log = await git.log(); return log.total; // Anzahl der Commits }); // Prüfe, ob es ungestagte Änderungen gibt ipcMain.handle('has-diffs', async (_e, folderObj) => { const git = simpleGit(folderObj.path); const status = await git.status(); // modified, not_added, deleted, etc. return status.files.length > 0; }); // Entferne das .git-Verzeichnis ipcMain.handle('remove-git-folder', async (_e, folderObj) => { const gitDir = path.join(folderObj.path, '.git'); if (fs.existsSync(gitDir)) { await fs.promises.rm(gitDir, { recursive: true, force: true }); } return; }); /* // Selected ipcMain.handle('get-selected', () => store.get('selected')); ipcMain.handle('set-selected', (_e, folder) => { store.set('selected', folder); return folder; }); */ // Commits holen ipcMain.handle('get-commits', async (_e, folderObj) => { const git = simpleGit(folderObj.path); // alle Commits holen const log = await git.log(['--all']); // aktuellen HEAD‐Hash ermitteln const fullHead = (await git.revparse(['--verify', 'HEAD'])).trim(); const head = fullHead.substring(0, 7); return { head, commits: log.all.map(c => ({ hash: c.hash.substring(0, 7), date: c.date, message: c.message })) }; }); // Diff ipcMain.handle('diff-commit', async (_e, folderObj, hash) => { const git = simpleGit(folderObj.path); return git.diff([`${hash}^!`]); }); // Revert ipcMain.handle('revert-commit', async (_e, folderObj, hash) => { const git = simpleGit(folderObj.path); await git.revert(hash, ['--no-edit']); }); //yo /** * Checkt das Arbeitsverzeichnis auf exakt den Zustand von `hash` aus. */ ipcMain.handle('checkout-commit', async (_e, folderObj, hash) => { const git = simpleGit(folderObj.path); // clean mode: alle lokalen Veränderungen verwerfen await git.checkout([hash, '--force']); }); // Snapshot ipcMain.handle('snapshot-commit', async (_e, folderObj, hash) => { const { canceled, filePaths } = await dialog.showOpenDialog({ title: 'Ordner auswählen zum Speichern des Snapshots', properties: ['openDirectory'] }); if (canceled || !filePaths[0]) return; const outDir = filePaths[0]; const baseName = path.basename(folderObj.path); const filePath = path.join(outDir, `${baseName}-${hash}.zip`); return new Promise((resolve, reject) => { exec( `git -C "${folderObj.path}" archive --format zip --output "${filePath}" ${hash}`, err => err ? reject(err) : resolve(filePath) ); }); }); // IPC für skymode ipcMain.handle('get-skymode', () => store.get('skymode')); ipcMain.handle('set-skymode', (_e, val) => { store.set('skymode', val); // sende an alle Fenster BrowserWindow.getAllWindows().forEach(win => { win.webContents.send('skymode-changed', val); }); }); ipcMain.handle('get-skip-git-prompt', () => store.get('skipGitPrompt')); ipcMain.handle('set-skip-git-prompt', (_e,val) => store.set('skipGitPrompt', val)); // Auto-Verzeichnisstruktur const IGNORED_NAMES = [ '.DS_Store', 'node_modules', '.git', 'dist', 'build', '.cache', 'out', '.venv', '.mypy_cache', '__pycache__', 'package-lock.json' ]; function isIgnored(name) { return IGNORED_NAMES.includes(name); } function walkDir(base, rel = '.') { const full = path.join(base, rel); let list = []; try { fs.readdirSync(full, { withFileTypes: true }).forEach(dirent => { if (isIgnored(dirent.name)) return; const entry = path.join(rel, dirent.name); if (dirent.isDirectory()) { list.push({ name: dirent.name, type: 'dir', children: walkDir(base, entry) }); } else { list.push({ name: dirent.name, type: 'file' }); } }); } catch (e) {} return list; } ipcMain.handle('get-folder-tree', async (_e, folderPath) => { try { return walkDir(folderPath, '.'); } catch { return []; } }); ipcMain.handle('commit-current-folder', async (_e, folderObj, message) => { folder = folderObj.path; try { debug(`Commit-Vorgang für ${folder} gestartet…`); const git = simpleGit(folder); // Prüfe: Gibt es was zu committen? const status = await git.status(); if ( status.not_added.length === 0 && status.created.length === 0 && status.deleted.length === 0 && status.modified.length === 0 && status.renamed.length === 0 ) { debug('Nichts zu committen.'); return { success: false, error: 'Nichts zu committen.' }; } // HEAD-Status prüfen let currentBranch = null; try { currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim(); debug(`Aktueller Branch: ${currentBranch}`); } catch (err) { debug('HEAD ist detached.'); } // Falls detached, **jetzt erst** alten Branch umbenennen und neuen master erzeugen if (!currentBranch || currentBranch === 'HEAD') { // HEAD ist detached, prüfe ob HEAD auf dem Tip von master ist! const headCommit = (await git.revparse(['HEAD'])).trim(); let masterCommit = null; let hasMaster = false; try { masterCommit = (await git.revparse(['refs/heads/master'])).trim(); hasMaster = true; } catch (e) { masterCommit = null; //wawa hasMaster = false; } if (hasMaster && headCommit === masterCommit) { // HEAD ist detached, aber zeigt exakt auf master! await git.checkout('master'); debug('[autoCommit] HEAD war detached, zeigte aber exakt auf master – jetzt zurück auf master.'); // **Return nicht vergessen, sonst geht der Branch-Move weiter** // ----> Das ist die Zeile die du wahrscheinlich vergessen hast! // Beende hier die Branch-Logik return { success: true }; }b // Ansonsten wie gehabt: if (hasMaster) { // HEAD ist detached, zeigt nicht auf master -> backup + neuer master const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupBranch = `backup-master-${timestamp}`; await git.branch(['-m', 'master', backupBranch]); debug(`[autoCommit] Alter master in ${backupBranch} umbenannt.`); await git.checkout(['-b', 'master']); debug('[autoCommit] Neuer master-Branch erstellt und ausgecheckt.'); } else { // Kein master vorhanden (erstmalig) await git.checkout(['-b', 'master']); debug('[autoCommit] Kein master-Branch vorhanden, neuer master erstellt.'); } } await git.add(['-A']); debug('Alle Änderungen gestaged.'); await git.commit(message || 'test'); debug('Commit erfolgreich erstellt.'); // Push hier ggf. noch auskommentiert lassen return { success: true }; } catch (err) { debug(`FEHLER beim Commit: ${err.message}`); return { success: false, error: err.message }; } }); ipcMain.handle('set-monitoring', async (_e, folderPath, monitoring) => { let folders = store.get('folders') || []; folders = folders.map(f => f.path === folderPath ? { ...f, monitoring } : f ); store.set('folders', folders); debug(`[STORE] Monitoring für ${folderPath}: ${monitoring}`); // Monitoring-Watcher starten/stoppen → gleich mehr dazu if (monitoring) { startMonitoringWatcher(folderPath, win); } else { stopMonitoringWatcher(folderPath); } return monitoring; }); ipcMain.handle('get-intelligent-commit-threshold', () => store.get('intelligentCommitThreshold')); ipcMain.handle('set-intelligent-commit-threshold', (_e, value) => { store.set('intelligentCommitThreshold', value); }); // … Ende der IPC-Handler … }); ipcMain.on('show-folder-context-menu', (event, folderPath) => { const win = BrowserWindow.fromWebContents(event.sender); const template = [ { label: 'Copy Folder Path', click: () => { clipboard.writeText(folderPath); } }, { label: 'Open Folder', click: () => { // öffnet den Ordner in der nativen Dateiansicht shell.openPath(folderPath); } } ]; const menu = Menu.buildFromTemplate(template); menu.popup({ window: win }); }); // clean up on exit app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });