From d0829f8c332dc12e5b5accf32ce25327eb3d2f7c Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Sat, 24 May 2025 14:48:58 +0200 Subject: [PATCH] auto-git: [change] main.js --- main.js | 884 -------------------------------------------------------- 1 file changed, 884 deletions(-) diff --git a/main.js b/main.js index c4f108d..e69de29 100644 --- a/main.js +++ b/main.js @@ -1,884 +0,0 @@ -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; -} - -// 2. Prompts für LLM bauen -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} - -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 in a SINGLE SENTENCE. 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, maxLen = 500) { - return new Promise((resolve, reject) => { - const child = spawn('ollama', [ - '--stream', - '--model', 'qwen2.5-coder:32b', - '--prompt', prompt - ]); - - let out = ''; - let resolved = false; - - child.stdout.on('data', chunk => { - const s = chunk.toString(); - out += s; - if (onDataChunk) onDataChunk(s); - - // Cut at first linebreak OR at maxLen - const firstLine = out.split('\n')[0]; - if (!resolved && (firstLine.length >= maxLen || out.includes('\n'))) { - resolved = true; - child.kill(); - resolve(firstLine.slice(0, maxLen).trim()); - } - }); - - child.stderr.on('data', chunk => { - if (!resolved) { - resolved = true; - reject(chunk.toString()); - } - }); - - child.on('close', () => { - if (!resolved) { - const firstLine = out.split('\n')[0]; - resolve(firstLine.slice(0, maxLen).trim()); - } - }); - - child.on('error', err => { - if (!resolved) { - resolved = true; - reject(err); - } - }); - }); -} - -async function squashCommitMessages(repoPath, commitMessage, hashes) { - const git = simpleGit(repoPath); - - // 1. Find the oldest commit in the sequence - const allCommits = (await git.log()).all; - const hashIdxs = hashes.map(h => allCommits.findIndex(c => c.hash.startsWith(h))); - if (hashIdxs.includes(-1)) throw new Error('Some commit(s) not found.'); - const oldestIdx = Math.min(...hashIdxs); - - // The commit *before* the oldest in the list is the "upstream" for rebase - const rebaseFrom = oldestIdx + 1 < allCommits.length ? allCommits[oldestIdx + 1].hash : '--root'; - - // 2. Create rebase-todo: all commits → squash into first - // Pick the oldest, squash the rest - const toSquash = allCommits.slice(0, oldestIdx + 1).reverse(); - const first = toSquash[0]; - const rest = toSquash.slice(1); - - const todoLines = [ - `pick ${first.hash} ${first.message.split('\n')[0]}`, - ...rest.map(c => `squash ${c.hash} ${c.message.split('\n')[0]}`) - ]; - - // Write the todo file - const tmpdir = require('os').tmpdir(); - const todoPath = require('path').join(tmpdir, `git-rebase-todo-${Date.now()}.txt`); - fs.writeFileSync(todoPath, todoLines.join('\n')); - - // Create a temporary file for the commit message - const msgPath = require('path').join(tmpdir, `llm-squash-msg-${Date.now()}.txt`); - fs.writeFileSync(msgPath, commitMessage); - - // Use GIT_SEQUENCE_EDITOR to inject the todo file, and GIT_EDITOR to inject the commit message - await new Promise((resolve, reject) => { - const proc = spawn('git', ['rebase', '-i', rebaseFrom], { - cwd: repoPath, - env: { - ...process.env, - GIT_SEQUENCE_EDITOR: `cat "${todoPath}" >`, - GIT_EDITOR: `cat "${msgPath}" >` - }, - stdio: 'inherit' - }); - proc.on('exit', code => { - try { - fs.unlinkSync(todoPath); - fs.unlinkSync(msgPath); - } catch {} - if (code === 0) resolve(); - else reject(new Error('git rebase failed with exit code ' + code)); - }); - }); -} - -/** - * Komplett-Workflow: Von Kandidaten bis Rewrite - */ - -async function runLLMCommitPipeline(folderPath, hashes) { - const prompt = await getPrompt(folderPath, hashes); - const llmOutput = (await streamLLMCommitMessages(prompt, chunk => process.stdout.write(chunk))).trim(); - - if (hashes.length === 1) { - // For a single commit: amend it in-place - const git = simpleGit(folderPath); - await git.raw(['commit', '--amend', '-m', llmOutput, '--no-edit', '--allow-empty']); - } else if (hashes.length > 1) { - // For multiple commits: squash & set message - await squashCommitMessages(folderPath, llmOutput, hashes); - } else { - throw new Error('No commits to rewrite.'); - } -} - -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(); -});