Refactor commit message rewording logic in main.js
This commit is contained in:
183
main.js
183
main.js
@@ -1023,27 +1023,178 @@ async function rewordCommitsSequentially(repoPath, commitMessageMap, hashes) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//---- 6. Workflow ----
|
//---- 6. Workflow ----
|
||||||
|
/**
|
||||||
|
* Rewords commit messages für alle angegebenen Hashes in einem einzigen
|
||||||
|
* interaktiven Rebase, indem jeweils “pick” durch “reword” ersetzt wird
|
||||||
|
* und dabei immer das gleiche, vertraute Editor-Script (editor-reword.js)
|
||||||
|
* zum Einsetzen der neuen Nachrichten verwendet wird.
|
||||||
|
*
|
||||||
|
* @param {string} repoPath - Pfad zum Repository
|
||||||
|
* @param {{ [hash: string]: string }} commitMessageMap - Mapping (fullHash oder 7-stelliger ShortHash) → neue Commit-Message
|
||||||
|
* @param {string[]} hashes - Liste aller Hashes, älteste zuerst
|
||||||
|
*/
|
||||||
|
async function rewordCommitsSequentially(repoPath, commitMessageMap, hashes) {
|
||||||
|
const git = simpleGit(repoPath);
|
||||||
|
|
||||||
|
// 1. Holen der gesamten Commit-Historie, um die übergebenen hashes chronologisch zu sortieren
|
||||||
|
const allCommits = (await git.log()).all;
|
||||||
|
const hashesOrdered = hashes
|
||||||
|
.map(h => allCommits.find(c => c.hash.startsWith(h)))
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const idxA = allCommits.findIndex(c => c.hash === a.hash);
|
||||||
|
const idxB = allCommits.findIndex(c => c.hash === b.hash);
|
||||||
|
return idxA - idxB;
|
||||||
|
})
|
||||||
|
.map(c => c.hash);
|
||||||
|
|
||||||
|
if (hashesOrdered.length === 0) {
|
||||||
|
console.warn('[rewordCommitsSequentially] Keine passenden Commits gefunden.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Full Hash des ältesten Commits ermitteln
|
||||||
|
const oldestHash = hashesOrdered[0];
|
||||||
|
|
||||||
|
// 3. Serielles Aborten eines eventuell laufenden Rebase, damit stets sauber gestartet wird
|
||||||
|
try {
|
||||||
|
await git.raw(['rebase', '--abort']);
|
||||||
|
} catch {
|
||||||
|
// Falls kein Rebase offen, ignorieren
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Pfad zum vertrauten Editor-Script:
|
||||||
|
// (editor-reword.js liest process.env.REBASE_COMMIT_MAP und ersetzt
|
||||||
|
// nur dann, wenn GIT_COMMIT im Map existiert.)
|
||||||
|
const editorScript = path.join(__dirname, 'editor-reword.js');
|
||||||
|
if (!fs.existsSync(editorScript)) {
|
||||||
|
throw new Error(`[rewordCommitsSequentially] Konnte editor-reword.js nicht finden unter ${editorScript}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. GIT_SEQUENCE_EDITOR: Ersetze in der Rebase-Todo-Liste alle “pick ”-Zeilen durch “reword ”
|
||||||
|
// Dadurch hält Git für jeden Commit an und ruft unseren GIT_EDITOR (editor-reword.js) auf.
|
||||||
|
// Unter macOS/Linux:
|
||||||
|
const sequenceEditor = `sed -i '' 's/^pick /reword /'`;
|
||||||
|
|
||||||
|
// 6. Setze Umgebungsvariablen für den Rebase-Aufruf
|
||||||
|
// - REBASE_COMMIT_MAP: JSON-String, den editor-reword.js parsen wird
|
||||||
|
// - GIT_SEQUENCE_EDITOR: oben definierter sed-Befehl
|
||||||
|
// - GIT_EDITOR: “node editor-reword.js” (wir übergeben den JSON-String über ENV)
|
||||||
|
const envVars = {
|
||||||
|
...process.env,
|
||||||
|
REBASE_COMMIT_MAP: JSON.stringify(commitMessageMap),
|
||||||
|
GIT_SEQUENCE_EDITOR: sequenceEditor,
|
||||||
|
GIT_EDITOR: `node "${editorScript}"`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 7. Interaktiver Rebase für <oldestHash>^ starten
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const proc = spawn('git', ['rebase', '-i', `${oldestHash}^`], {
|
||||||
|
cwd: repoPath,
|
||||||
|
env: envVars,
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
proc.on('exit', code => {
|
||||||
|
if (code === 0) {
|
||||||
|
console.log('[AutoGit] Reworded commits erfolgreich. ✔');
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`git rebase -i ist mit Exit-Code ${code} fehlgeschlagen.`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Führt das Umschreiben aller gesammelten LLM-Kandidaten per interaktivem Rebase durch.
|
||||||
|
* Dabei wird unser vertrautes editor-reword.js-Skript erneut aufgerufen, indem wir
|
||||||
|
* REBASE_COMMIT_MAP mit dem kompletten Map-Objekt füllen und “pick” → “reword” umwandeln.
|
||||||
|
*
|
||||||
|
* @param {object} folderObj - Das Folder-Objekt aus dem Store
|
||||||
|
* @param {BrowserWindow} win - Referenz auf das Electron-Fenster (für Notifications/Streaming)
|
||||||
|
*/
|
||||||
async function runLLMCommitRewrite(folderObj, win) {
|
async function runLLMCommitRewrite(folderObj, win) {
|
||||||
if(!folderObj.needsRelocation){
|
// Initialisiere Felder, falls undefined
|
||||||
const hashes = folderObj.llmCandidates;
|
folderObj.llmCandidates = folderObj.llmCandidates || [];
|
||||||
const birthday = folderObj.firstCandidateBirthday;
|
folderObj.pendingLLMCandidates = folderObj.pendingLLMCandidates || [];
|
||||||
const folderPath = folderObj.path;
|
folderObj.inProgressLLMCandidates = folderObj.inProgressLLMCandidates || [];
|
||||||
folderObj.llmCandidates = [];
|
folderObj.rebasing = folderObj.rebasing || false;
|
||||||
folderObj.firstCandidateBirthday = null;
|
folderObj.linesChanged = folderObj.linesChanged || 0;
|
||||||
folderObj.linesChanged = 0;
|
|
||||||
const folders = store.get('folders') || [];
|
// Abbrechen, falls Ordner verlagert ist oder bereits ein Rebase läuft
|
||||||
const idx = folders.findIndex(f => f.path === folderObj.path);
|
if (folderObj.needsRelocation || folderObj.rebasing) return;
|
||||||
if (idx !== -1) {
|
|
||||||
folders[idx] = folderObj;
|
// Rebasing-Flag setzen
|
||||||
store.set('folders', folders);
|
folderObj.rebasing = true;
|
||||||
}
|
|
||||||
const prompt = await generateLLMCommitMessages(folderPath, hashes);
|
// 1) Falls in pendingLLMCandidates Hashes liegen, diese zuerst mit aufnehmen und leeren
|
||||||
|
if (folderObj.pendingLLMCandidates.length > 0) {
|
||||||
|
folderObj.llmCandidates.push(...folderObj.pendingLLMCandidates);
|
||||||
|
folderObj.pendingLLMCandidates = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Sicherstellen, dass wir eine aktuelle Kopie aller Kandidaten haben, und zurücksetzen
|
||||||
|
folderObj.inProgressLLMCandidates = folderObj.llmCandidates.slice();
|
||||||
|
folderObj.llmCandidates = [];
|
||||||
|
folderObj.linesChanged = 0;
|
||||||
|
folderObj.firstCandidateBirthday = null;
|
||||||
|
|
||||||
|
// 3) Sofort in den Store schreiben, damit UI das laufende Rebase anzeigt
|
||||||
|
let allFolders = store.get('folders') || [];
|
||||||
|
const idx = allFolders.findIndex(f => f.path === folderObj.path);
|
||||||
|
if (idx !== -1) {
|
||||||
|
allFolders[idx] = folderObj;
|
||||||
|
store.set('folders', allFolders);
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderPath = folderObj.path;
|
||||||
|
const hashesToProcess = folderObj.inProgressLLMCandidates.slice(); // Kopie
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 4) Prompt für LLM erstellen
|
||||||
|
const prompt = await generateLLMCommitMessages(folderPath, hashesToProcess);
|
||||||
|
|
||||||
|
// 5) Streaming-Call
|
||||||
const llmRaw = await streamLLMCommitMessages(prompt, chunk => process.stdout.write(chunk), win);
|
const llmRaw = await streamLLMCommitMessages(prompt, chunk => process.stdout.write(chunk), win);
|
||||||
|
|
||||||
|
// 6) JSON-Antwort parsen
|
||||||
const commitList = parseLLMCommitMessages(llmRaw);
|
const commitList = parseLLMCommitMessages(llmRaw);
|
||||||
const messageMap = {};
|
const messageMap = {};
|
||||||
for (const entry of commitList) messageMap[entry.commit] = entry.newMessage;
|
for (const entry of commitList) {
|
||||||
await rewordCommitsSequentially(folderPath, messageMap, hashes);
|
messageMap[entry.commit] = entry.newMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7) EIN EINZIGER Rebase-Aufruf für alle hashesToProcess
|
||||||
|
await rewordCommitsSequentially(folderPath, messageMap, hashesToProcess);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[runLLMCommitRewrite] Fehler:', err);
|
||||||
|
// Falls etwas schiefläuft, die Hashes zurück in llmCandidates schieben
|
||||||
|
for (const hash of hashesToProcess) {
|
||||||
|
if (!folderObj.llmCandidates.includes(hash)) {
|
||||||
|
folderObj.llmCandidates.push(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// 8) Rebase-Flag zurücksetzen und Store updaten
|
||||||
|
folderObj.rebasing = false;
|
||||||
|
let updated = store.get('folders') || [];
|
||||||
|
const i = updated.findIndex(f => f.path === folderObj.path);
|
||||||
|
if (i !== -1) {
|
||||||
|
updated[i] = folderObj;
|
||||||
|
store.set('folders', updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9) Renderer benachrichtigen
|
||||||
win.webContents.send('repo-updated', folderObj.path);
|
win.webContents.send('repo-updated', folderObj.path);
|
||||||
|
debug(`[runLLMCommitRewrite] Fertig. llmCandidates = ${JSON.stringify(folderObj.llmCandidates)}`);
|
||||||
|
|
||||||
|
// 10) Wenn während des Rebases neue Kandidaten hinzugekommen sind, direkt weiterfahren
|
||||||
|
if (folderObj.llmCandidates.length > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
runLLMCommitRewrite(folderObj, win);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user