1
0

Refactor and enhance gitignore handling in auto-git script

This commit is contained in:
2025-06-01 06:06:09 +02:00
parent fff9c19736
commit df0342c3c3

147
main.js
View File

@@ -314,81 +314,89 @@ const IGNORED_NAMES = [
// Maven Wrapper // Maven Wrapper
'mvnw.cmd', 'mvnw', '.mvn/wrapper/maven-wrapper.jar' 'mvnw.cmd', 'mvnw', '.mvn/wrapper/maven-wrapper.jar'
]; ];
/**
* Liest die .gitignore im Repo (falls vorhanden) und gibt eine "ignore"-Instanz
* zurück, die alle aktuellen Einträge kennt. Sollte keine .gitignore existieren,
* wird eine leere Instanz zurückgegeben.
*/
async function loadGitignoreFilter(folderPath) {
const gitignorePath = path.join(folderPath, '.gitignore');
let raw;
try {
raw = await fs.readFile(gitignorePath, 'utf8');
} catch (e) {
// Wenn keine .gitignore existiert, gibt's eine leere Instanz
raw = '';
}
// ignore() versteht Git-Ignore-Syntax (Kommentare, Leerzeilen, Glob-Pattern etc.)
const ig = ignore();
ig.add(raw.split(/\r?\n/));
return ig;
}
/** /**
* Überprüft, ob ein gegebener Pfad einem Eintrag in IGNORED_NAMES entspricht. * Wie zuvor: Prüft, ob ein relativer Pfad (innerhalb folderPath) mit einem
* Wir testen dabei, ob der Dateiname oder ein Teil des Pfades mit einem der * Eintrag in IGNORED_NAMES übereinstimmt. (Wildcards, Ordnernamen, Dateiendungen etc.)
* Einträge übereinstimmt. Wildcards (z.B. "*.log") werden mit minimiertem Regex-Check abgeglichen.
*/ */
function isIgnored(filePathRelative) { function isIgnoredByList(filePathRelative) {
const basename = path.basename(filePathRelative); const basename = path.basename(filePathRelative);
return IGNORED_NAMES.some((pattern) => { return IGNORED_NAMES.some(pattern => {
if (pattern.includes('*')) { if (pattern.includes('*')) {
// Einfacher Umgang mit "*"-Wildcard: z. B. "*.log" // Einfacher Wildcard-Abgleich: "*.log" -> /^.*\.log$/
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$'); const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
return regex.test(basename); return regex.test(basename);
} }
// genaues Match am Dateinamen oder irgendwo im Pfad
return basename === pattern || filePathRelative.split(path.sep).includes(pattern); return basename === pattern || filePathRelative.split(path.sep).includes(pattern);
}); });
} }
/** /**
* Fügt einen Eintrag in die .gitignore im Zielordner hinzu, falls er noch nicht existiert. * Schreibt einen Eintrag in .gitignore, falls noch nicht vorhanden.
*/ */
async function ensureIgnoredInGitignore(folderPath, entry) { async function ensureIgnoredInGitignore(folderPath, entry) {
const gitignorePath = path.join(folderPath, '.gitignore'); const gitignorePath = path.join(folderPath, '.gitignore');
let content = ''; let content = '';
try { try {
content = await fs.readFile(gitignorePath, { encoding: 'utf8' }); content = await fs.readFile(gitignorePath, 'utf8');
} catch (err) { } catch (e) {
// .gitignore existiert noch nicht => wird beim Schreiben erstellt
content = ''; content = '';
} }
const lines = content.split(/\r?\n/); const lines = content.split(/\r?\n/).map(l => l.trim());
if (!lines.includes(entry)) { if (!lines.includes(entry)) {
const newContent = content.trim().length > 0 const newContent = content.trim().length > 0
? content.trim() + '\n' + entry + '\n' ? content.trim() + '\n' + entry + '\n'
: entry + '\n'; : entry + '\n';
await fs.writeFile(gitignorePath, newContent, { encoding: 'utf8' }); await fs.writeFile(gitignorePath, newContent, 'utf8');
debug(`[IGNORE] Eintrag "${entry}" zu .gitignore hinzugefügt in ${folderPath}`); debug(`[IGNORE] Eintrag "${entry}" zu .gitignore hinzugefügt in ${folderPath}`);
return true; return true;
} }
return false; return false;
} }
/** /**
* Kombinierte Funktion: Initialer Commit und Watcher in einem. * Die kombinierte Funktion für initialen Commit + Watcher.
*/ */
function startMonitoringWatcher(folderPath, win) { async function startMonitoringWatcher(folderPath, win) {
if (monitoringWatchers.has(folderPath)) return; if (monitoringWatchers.has(folderPath)) return;
const watcher = chokidar.watch(folderPath, {
ignored: /(^|[\/\\])\.git/,
ignoreInitial: true,
persistent: true,
depth: 99,
awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 }
});
const git = simpleGit(folderPath); const git = simpleGit(folderPath);
// 1. Lade oder lege eine .gitignore-Filterinstanz an
let igFilter = await loadGitignoreFilter(folderPath);
/** /**
* Führt einen Git-Status-Check durch, filtert ignorierbare Dateien raus und * Führt Git-Status-Check aus, filtert Dateien heraus,
* schreibt sie ggfs. in die .gitignore, bevor die verbleibenden Änderungen committed werden. * die in IGNORED_NAMES stehen, schreibt sie ggf. in .gitignore,
* und committet dann verbleibende Änderungen.
*/ */
async function doAutoCommitWithIgnoreCheck() { async function doAutoCommitWithIgnoreCheck() {
const status = await git.status(); const status = await git.status();
// Dateien, die ignoriert werden sollen (nach IGNORED_NAMES) // 2. Alle not_added/created-Dateien, die in unserer IGNORED_NAMES-Liste stehen:
const toIgnore = [ const toIgnore = [ ...status.not_added, ...status.created ]
...status.not_added, .filter(rel => isIgnoredByList(rel));
...status.created
].filter(filePathRel => isIgnored(filePathRel));
// Dateien, die tatsächlich committed werden sollen // 3. Der Rest geht in toCommit:
const toCommit = { const toCommit = {
not_added: status.not_added.filter(p => !toIgnore.includes(p)), not_added: status.not_added.filter(p => !toIgnore.includes(p)),
created: status.created.filter(p => !toIgnore.includes(p)), created: status.created.filter(p => !toIgnore.includes(p)),
@@ -397,26 +405,28 @@ function startMonitoringWatcher(folderPath, win) {
renamed: status.renamed.slice() renamed: status.renamed.slice()
}; };
// 1. Ignorierbare Dateien in .gitignore schreiben // 4. Zuerst .gitignore aktualisieren, falls nötig
let ignoreWritten = false; let ignoreWritten = false;
for (const fileRel of toIgnore) { for (const relPath of toIgnore) {
// Den Eintrag so in .gitignore setzen, dass er relativ zum Repo-Wurzel passt const parts = relPath.split(path.sep);
// z.B. "node_modules/myLib" => "node_modules/" const entry = parts.length > 1 ? parts[0] + '/' : relPath;
const parts = fileRel.split(path.sep); // "/" anhängen, damit Ordner wie "node_modules/" korrekt ignoriert werden
const entry = parts.length > 1 ? parts[0] : fileRel;
const didWrite = await ensureIgnoredInGitignore(folderPath, entry); const didWrite = await ensureIgnoredInGitignore(folderPath, entry);
if (didWrite) ignoreWritten = true; if (didWrite) ignoreWritten = true;
} }
if (ignoreWritten) { if (ignoreWritten) {
// .gitignore wurde angepasst → committen
await git.add('.gitignore'); await git.add('.gitignore');
await git.commit('auto-git: Aktualisiere .gitignore um ignorierte Dateien'); await git.commit('auto-git: Aktualisiere .gitignore um ignorierte Dateien');
win.webContents.send('repo-updated', folderPath); win.webContents.send('repo-updated', folderPath);
debug('[MONITOR] .gitignore Commit ausgeführt');
// nach dem Schreiben in .gitignore möchte man nochmal den Status neu holen // Neue .gitignore einladen, damit unser Filter aktuell ist
return await doAutoCommitWithIgnoreCheck(); igFilter = await loadGitignoreFilter(folderPath);
// Danach Status erneut prüfen (Rekursionsaufruf), weil sich nun ignore-Muster geändert haben
return doAutoCommitWithIgnoreCheck();
} }
// 2. Commit für die verbleibenden Änderungen (falls vorhanden) // 5. Wenn keine .gitignore-Anpassung nötig war, committen wir alle echten Änderungen:
const hasChanges = const hasChanges =
toCommit.not_added.length > 0 || toCommit.not_added.length > 0 ||
toCommit.created.length > 0 || toCommit.created.length > 0 ||
@@ -425,7 +435,6 @@ function startMonitoringWatcher(folderPath, win) {
toCommit.renamed.length > 0; toCommit.renamed.length > 0;
if (hasChanges) { if (hasChanges) {
// Build-Message aus den gefilterten Status-Feldern
const fakeStatus = { const fakeStatus = {
not_added: toCommit.not_added, not_added: toCommit.not_added,
created: toCommit.created, created: toCommit.created,
@@ -440,34 +449,60 @@ function startMonitoringWatcher(folderPath, win) {
} }
} }
// --- Initialer Commit-Check --- // 6. Chokidar-Watcher startet – nutzt eine Funktion, die sowohl .gitignore-Muster
// abfragt als auch das .git-Verzeichnis immer aussperrt.
const watcher = chokidar.watch(folderPath, {
ignored: (filePath) => {
// Immer das .git-Verzeichnis ausschließen
const isInDotGit = /(^|[\/\\])\.git(?:[\/\\]|$)/.test(filePath);
if (isInDotGit) return true;
// Relativer Pfad zum Repo
const rel = path.relative(folderPath, filePath);
// 1) Wenn die Datei in der .gitignore steht → ignorieren
if (igFilter.ignores(rel)) return true;
// 2) Ansonsten wird die Änderung durch unseren Code ohnehin
// beim Commit-Check berücksichtigt (wir ignorieren nur, was auch wirklich in IGNORED_NAMES ist).
return false;
},
ignoreInitial: true,
persistent: true,
depth: 99,
awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 }
});
// 7. Initialer Commit-Check
(async () => { (async () => {
debug(`[MONITOR] Starte initialen Commit-Check für ${folderPath}`); debug(`[MONITOR] Starte initialen Commit-Check für ${folderPath}`);
await doAutoCommitWithIgnoreCheck(); await doAutoCommitWithIgnoreCheck();
})(); })();
// --- Watcher: bei jedem Dateisystem-Event --- // 8. Bei jedem Event: Prüfen, ob Pfad in .gitignore gehört,
// sonst normal doAutoCommitWithIgnoreCheck() aufrufen.
watcher.on('all', async (event, filePathAbsolute) => { watcher.on('all', async (event, filePathAbsolute) => {
// Relativer Pfad zum Repository const rel = path.relative(folderPath, filePathAbsolute);
const filePathRel = path.relative(folderPath, filePathAbsolute);
// Zuerst prüfen, ob die Änderung eine Datei betrifft, die wir ignorieren wollen // a) Prüfe, ob dieser Pfad durch die aktuell geladene .gitignore
if (isIgnored(filePathRel)) { // bereits ausgeschlossen ist (z.B. node_modules/foo.js):
// Sicherstellen, dass der Eintrag in .gitignore steht if (igFilter.ignores(rel)) {
const entry = filePathRel.split(path.sep)[0]; // Falls wir bisher noch nicht in .gitignore hatten, dort hinzufügen:
// (Aber eigentlich sollte .gitignore aktuell sein, weil doAutoCommitWithIgnoreCheck()
// ja erst dafür gesorgt hat.)
const parts = rel.split(path.sep);
const entry = parts.length > 1 ? parts[0] + '/' : rel;
const didWrite = await ensureIgnoredInGitignore(folderPath, entry); const didWrite = await ensureIgnoredInGitignore(folderPath, entry);
if (didWrite) { if (didWrite) {
// Wenn in .gitignore geschrieben wurde, committen wir das und senden Update // Neue .gitignore-Muster einladen
igFilter = await loadGitignoreFilter(folderPath);
await git.add('.gitignore'); await git.add('.gitignore');
await git.commit('auto-git: Hinzufügen zu .gitignore'); await git.commit('auto-git: Hinzufügen zu .gitignore');
win.webContents.send('repo-updated', folderPath); win.webContents.send('repo-updated', folderPath);
debug(`[WATCHER] "${entry}" zu .gitignore hinzugefügt und committed`); debug(`[WATCHER] "${entry}" zu .gitignore hinzugefügt und committed`);
} }
// Änderung wird nicht weiter committed
return; return;
} }
// Ansonsten ganz normal: Status abfragen und committen // b) Ansonsten ganz normal alle Änderungen committen
await doAutoCommitWithIgnoreCheck(); await doAutoCommitWithIgnoreCheck();
}); });