506 Zeilen
15 KiB
JavaScript
506 Zeilen
15 KiB
JavaScript
/**
|
|
* TASKMATE - Git Route
|
|
* =====================
|
|
* API-Endpoints für Git-Operationen
|
|
*/
|
|
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const { getDb } = require('../database');
|
|
const logger = require('../utils/logger');
|
|
const gitService = require('../services/gitService');
|
|
const giteaService = require('../services/giteaService');
|
|
|
|
/**
|
|
* Hilfsfunktion: Anwendung für Projekt abrufen
|
|
*/
|
|
function getApplicationForProject(projectId) {
|
|
const db = getDb();
|
|
return db.prepare('SELECT * FROM applications WHERE project_id = ?').get(projectId);
|
|
}
|
|
|
|
/**
|
|
* POST /api/git/clone
|
|
* Repository klonen
|
|
*/
|
|
router.post('/clone', async (req, res) => {
|
|
try {
|
|
const { projectId, repoUrl, localPath, branch } = req.body;
|
|
|
|
if (!localPath) {
|
|
return res.status(400).json({ error: 'localPath ist erforderlich' });
|
|
}
|
|
|
|
if (!repoUrl) {
|
|
return res.status(400).json({ error: 'repoUrl ist erforderlich' });
|
|
}
|
|
|
|
// Clone ausführen
|
|
const result = await gitService.cloneRepository(repoUrl, localPath, { branch });
|
|
|
|
if (result.success && projectId) {
|
|
// Anwendung aktualisieren
|
|
const db = getDb();
|
|
db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId);
|
|
}
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Klonen:', error);
|
|
res.status(500).json({ error: 'Serverfehler', details: error.message });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/git/status/:projectId
|
|
* Git-Status für ein Projekt abrufen
|
|
*/
|
|
router.get('/status/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
const result = gitService.getStatus(application.local_path);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen des Status:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/pull/:projectId
|
|
* Pull für ein Projekt ausführen
|
|
*/
|
|
router.post('/pull/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { branch } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
// Fetch zuerst
|
|
gitService.fetchRemote(application.local_path);
|
|
|
|
// Dann Pull
|
|
const result = gitService.pullChanges(application.local_path, { branch });
|
|
|
|
if (result.success) {
|
|
// Sync-Zeitpunkt aktualisieren
|
|
const db = getDb();
|
|
db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId);
|
|
}
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Pull:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/push/:projectId
|
|
* Push für ein Projekt ausführen
|
|
*/
|
|
router.post('/push/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { branch } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
// Prüfe ob Remote existiert
|
|
if (!gitService.hasRemote(application.local_path)) {
|
|
return res.json({
|
|
success: false,
|
|
error: 'Kein Remote konfiguriert. Bitte Repository zuerst vorbereiten.'
|
|
});
|
|
}
|
|
|
|
// Versuche normalen Push, falls das fehlschlägt wegen fehlendem Upstream, push mit -u
|
|
let result = gitService.pushChanges(application.local_path, { branch });
|
|
|
|
// Falls Push wegen fehlendem Upstream fehlschlägt, versuche mit -u
|
|
if (!result.success && result.error && result.error.includes('no upstream')) {
|
|
const currentBranch = branch || 'main';
|
|
result = gitService.pushWithUpstream(application.local_path, currentBranch);
|
|
}
|
|
|
|
if (result.success) {
|
|
// Sync-Zeitpunkt aktualisieren
|
|
const db = getDb();
|
|
db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId);
|
|
}
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Push:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/commit/:projectId
|
|
* Commit für ein Projekt erstellen
|
|
*/
|
|
router.post('/commit/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { message, stageAll } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
if (!message) {
|
|
return res.status(400).json({ error: 'Commit-Nachricht ist erforderlich' });
|
|
}
|
|
|
|
// Optional: Alle Änderungen stagen
|
|
if (stageAll !== false) {
|
|
const stageResult = gitService.stageAll(application.local_path);
|
|
if (!stageResult.success) {
|
|
return res.json(stageResult);
|
|
}
|
|
}
|
|
|
|
// Commit erstellen
|
|
const result = gitService.commit(application.local_path, message);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Commit:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/git/commits/:projectId
|
|
* Commit-Historie für ein Projekt abrufen
|
|
*/
|
|
router.get('/commits/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const limit = parseInt(req.query.limit) || 20;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
const result = gitService.getCommitHistory(application.local_path, limit);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen der Commits:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/git/branches/:projectId
|
|
* Branches für ein Projekt abrufen
|
|
*/
|
|
router.get('/branches/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
const result = gitService.getBranches(application.local_path);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen der Branches:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/checkout/:projectId
|
|
* Branch wechseln
|
|
*/
|
|
router.post('/checkout/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { branch } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
if (!branch) {
|
|
return res.status(400).json({ error: 'Branch ist erforderlich' });
|
|
}
|
|
|
|
const result = gitService.checkoutBranch(application.local_path, branch);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Branch-Wechsel:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/fetch/:projectId
|
|
* Fetch von Remote ausführen
|
|
*/
|
|
router.post('/fetch/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
const result = gitService.fetchRemote(application.local_path);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Fetch:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/stage/:projectId
|
|
* Alle Änderungen stagen
|
|
*/
|
|
router.post('/stage/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
const result = gitService.stageAll(application.local_path);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Stagen:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/git/remote/:projectId
|
|
* Remote-URL abrufen
|
|
*/
|
|
router.get('/remote/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
const result = gitService.getRemoteUrl(application.local_path);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Abrufen der Remote-URL:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/validate-path
|
|
* Pfad validieren
|
|
*/
|
|
router.post('/validate-path', (req, res) => {
|
|
try {
|
|
const { path } = req.body;
|
|
|
|
if (!path) {
|
|
return res.status(400).json({ error: 'Pfad ist erforderlich' });
|
|
}
|
|
|
|
const isAccessible = gitService.isPathAccessible(path);
|
|
const isRepo = isAccessible ? gitService.isGitRepository(path) : false;
|
|
const hasRemote = isRepo ? gitService.hasRemote(path) : false;
|
|
|
|
res.json({
|
|
valid: isAccessible,
|
|
isRepository: isRepo,
|
|
hasRemote: hasRemote,
|
|
containerPath: gitService.windowsToContainerPath(path)
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler bei der Pfad-Validierung:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/prepare/:projectId
|
|
* Repository für Gitea vorbereiten (init, remote hinzufügen)
|
|
*/
|
|
router.post('/prepare/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { repoUrl, branch } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
if (!repoUrl) {
|
|
return res.status(400).json({ error: 'repoUrl ist erforderlich' });
|
|
}
|
|
|
|
const result = gitService.prepareForGitea(application.local_path, repoUrl, { branch });
|
|
|
|
if (result.success) {
|
|
logger.info(`Repository vorbereitet für Projekt ${projectId}: ${repoUrl}`);
|
|
}
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Vorbereiten des Repositories:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/set-remote/:projectId
|
|
* Remote für ein Projekt setzen/aktualisieren
|
|
*/
|
|
router.post('/set-remote/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { repoUrl } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
if (!repoUrl) {
|
|
return res.status(400).json({ error: 'repoUrl ist erforderlich' });
|
|
}
|
|
|
|
// Prüfe ob Git-Repo existiert
|
|
if (!gitService.isGitRepository(application.local_path)) {
|
|
// Initialisiere Repository
|
|
const initResult = gitService.initRepository(application.local_path);
|
|
if (!initResult.success) {
|
|
return res.json(initResult);
|
|
}
|
|
}
|
|
|
|
// Remote setzen
|
|
const result = gitService.setRemote(application.local_path, repoUrl);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Setzen des Remotes:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/init-push/:projectId
|
|
* Initialen Push mit Upstream-Tracking
|
|
* Body: { targetBranch?: string, force?: boolean }
|
|
* - targetBranch: Optional - Ziel-Branch auf Remote (z.B. "main" auch wenn lokal "master")
|
|
* - force: Optional - Force-Push um Remote zu überschreiben
|
|
*/
|
|
router.post('/init-push/:projectId', async (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { targetBranch, force } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
// targetBranch kann null sein - dann wird der lokale Branch-Name verwendet
|
|
// force: boolean - überschreibt Remote-Branch bei Konflikten
|
|
const result = gitService.pushWithUpstream(application.local_path, targetBranch || null, 'origin', force === true);
|
|
|
|
if (result.success) {
|
|
// Sync-Zeitpunkt aktualisieren
|
|
const db = getDb();
|
|
db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId);
|
|
|
|
// Default-Branch in Gitea aktualisieren wenn der gepushte Branch abweicht
|
|
if (application.gitea_repo_url && result.branch) {
|
|
try {
|
|
// Owner/Repo aus URL extrahieren (z.B. https://gitea.../AegisSight/TaskMate.git)
|
|
const urlMatch = application.gitea_repo_url.match(/\/([^\/]+)\/([^\/]+?)(?:\.git)?$/);
|
|
if (urlMatch) {
|
|
const owner = urlMatch[1];
|
|
const repoName = urlMatch[2];
|
|
const actualBranch = result.branch;
|
|
|
|
// Default-Branch in Gitea setzen
|
|
const updateResult = await giteaService.updateRepository(owner, repoName, {
|
|
defaultBranch: actualBranch
|
|
});
|
|
|
|
if (updateResult.success) {
|
|
logger.info(`Default-Branch in Gitea auf '${actualBranch}' gesetzt für ${owner}/${repoName}`);
|
|
result.giteaUpdated = true;
|
|
result.defaultBranch = actualBranch;
|
|
} else {
|
|
logger.warn(`Konnte Default-Branch in Gitea nicht aktualisieren: ${updateResult.error}`);
|
|
result.giteaUpdated = false;
|
|
}
|
|
}
|
|
} catch (giteaError) {
|
|
logger.warn('Fehler beim Aktualisieren des Default-Branch in Gitea:', giteaError);
|
|
result.giteaUpdated = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim initialen Push:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/git/rename-branch/:projectId
|
|
* Branch umbenennen
|
|
* Body: { oldName: string, newName: string }
|
|
*/
|
|
router.post('/rename-branch/:projectId', (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { oldName, newName } = req.body;
|
|
const application = getApplicationForProject(projectId);
|
|
|
|
if (!application) {
|
|
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
|
}
|
|
|
|
if (!oldName || !newName) {
|
|
return res.status(400).json({ error: 'oldName und newName sind erforderlich' });
|
|
}
|
|
|
|
const result = gitService.renameBranch(application.local_path, oldName, newName);
|
|
res.json(result);
|
|
} catch (error) {
|
|
logger.error('Fehler beim Umbenennen des Branches:', error);
|
|
res.status(500).json({ error: 'Serverfehler' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|