Commits vergleichen

..

2 Commits

Autor SHA1 Nachricht Datum
HG
dad07c7879 Änderung Autor 2025-12-29 19:20:26 +00:00
627beb1353 Update Gitea-Sektion:
Branch-Auswahl
2025-12-29 19:02:44 +00:00
34 geänderte Dateien mit 4221 neuen und 60 gelöschten Zeilen

Datei anzeigen

@ -1,6 +1,142 @@
TASKMATE - CHANGELOG TASKMATE - CHANGELOG
==================== ====================
================================================================================
29.12.2025 - Gitea: Commit-Autor = eingeloggter Benutzer
================================================================================
BUGFIX: COMMIT-AUTOR WAR FALSCH
--------------------------------------------------------------------------------
- Problem: Bei Commits wurde "Claude Projekt Manager" als Autor angezeigt
(aus der lokalen Git-Konfiguration des Benutzers)
- Lösung: Der eingeloggte TaskMate-Benutzer wird jetzt als Autor verwendet
ÄNDERUNGEN
--------------------------------------------------------------------------------
- backend/services/gitService.js: commit() akzeptiert jetzt author-Parameter
* Verwendet git commit --author="Name <email>" -m "Nachricht"
* Email wird generiert: benutzername@taskmate.local
- backend/routes/git.js: Commit-Route übergibt req.user als Autor
* Verwendet display_name oder username als Autorname
- frontend/sw.js: Cache-Version auf 124 erhöht
================================================================================
29.12.2025 - Gitea: Branch umbenennen + UI-Aufräumung
================================================================================
FEATURE: LOKALEN BRANCH UMBENENNEN
--------------------------------------------------------------------------------
- Neuer Stift-Button neben dem Branch-Dropdown
- Modal zum Umbenennen des aktuellen Branches (z.B. "master" → "main")
- Backend-Endpoint: POST /api/git/rename-branch/:projectId
UI-AUFRÄUMUNG
--------------------------------------------------------------------------------
- "Ahead/Behind" Anzeige entfernt (war verwirrend)
- Branch-Bereich aufgeräumt mit Rename-Button
ÄNDERUNGEN
--------------------------------------------------------------------------------
- frontend/index.html:
* Branch-Select-Group mit Rename-Button
* Neues Rename-Branch-Modal
* Ahead/Behind entfernt
- frontend/css/gitea.css:
* .branch-select-group, .btn-small, .btn-icon Styles
* .status-badge.ahead und #git-ahead-behind entfernt
- frontend/js/gitea.js:
* openRenameBranchModal(), executeRenameBranch() hinzugefügt
* Ahead/Behind-Rendering entfernt
- frontend/js/api.js: gitRenameBranch() hinzugefügt
- backend/services/gitService.js: renameBranch() Funktion
- backend/routes/git.js: rename-branch Endpoint
- frontend/sw.js: Cache-Version auf 123 erhöht
================================================================================
29.12.2025 - Gitea: Force-Push Option
================================================================================
FEATURE: FORCE-PUSH UM REMOTE ZU ÜBERSCHREIBEN
--------------------------------------------------------------------------------
- Problem: Push auf Remote-Branch mit unterschiedlicher Historie schlug fehl
(z.B. wenn Gitea eine README erstellt hat und man master→main pushen will)
- Lösung: Force-Push Option im Push-Modal hinzugefügt
ÄNDERUNGEN
--------------------------------------------------------------------------------
- frontend/index.html: Checkbox "Force Push (Remote überschreiben)" im Push-Modal
- frontend/css/gitea.css: .form-hint.warning Style für Warnung
- frontend/js/gitea.js: Force-Flag wird an API übergeben
- frontend/js/api.js: gitInitPush() akzeptiert force Parameter
- backend/services/gitService.js: pushWithUpstream() mit --force Flag
- backend/routes/git.js: init-push Endpoint akzeptiert force Parameter
- frontend/sw.js: Cache-Version auf 122 erhöht
================================================================================
29.12.2025 - Gitea: Bugfix Ziel-Branch Auswahl
================================================================================
BUGFIX: PUSH MIT ZIEL-BRANCH FUNKTIONIERTE NICHT
--------------------------------------------------------------------------------
- Problem: Bei Auswahl eines anderen Ziel-Branches (z.B. "main" statt "master")
wurde trotzdem der reguläre Push verwendet, der die Auswahl ignorierte
- Ursache: executePush() versuchte immer zuerst den normalen Push, der den
bestehenden Upstream verwendet, nicht den ausgewählten Target-Branch
LÖSUNG
--------------------------------------------------------------------------------
- frontend/js/gitea.js: executePush() Logik korrigiert
* Wenn Ziel-Branch != lokaler Branch → verwendet immer init-push mit Mapping
* Nur bei gleichem Branch-Namen → normaler Push mit Fallback
* Zeigt Info-Toast mit "Push von X nach Y"
- frontend/sw.js: Cache-Version auf 121 erhöht
================================================================================
29.12.2025 - Gitea: Ziel-Branch Auswahl beim Push
================================================================================
FEATURE: ZIEL-BRANCH AUSWAHL BEIM PUSH
--------------------------------------------------------------------------------
- Neues Push-Modal mit Branch-Auswahl vor dem Push
- Benutzer können jetzt wählen auf welchen Remote-Branch gepusht wird
- Z.B. lokaler Branch "master" kann als "main" auf Gitea gepusht werden
- Auswahl: "Gleicher Name wie lokal", "main", "master", "develop"
ÄNDERUNGEN
--------------------------------------------------------------------------------
- backend/services/gitService.js: pushWithUpstream() unterstützt jetzt targetBranch
* Branch-Mapping: `git push -u origin master:main` möglich
* Gibt sowohl localBranch als auch remoteBranch im Ergebnis zurück
- backend/routes/git.js: init-push Endpoint akzeptiert targetBranch Parameter
- frontend/js/api.js: gitInitPush() mit targetBranch Parameter
- frontend/js/gitea.js:
* Push-Button öffnet jetzt Modal statt direkt zu pushen
* Neue Methoden: openPushModal(), executePush()
- frontend/index.html: Neues Push-Modal mit Branch-Auswahl
- frontend/css/gitea.css: .form-static-value Style hinzugefügt
- frontend/sw.js: Cache-Version auf 120 erhöht
================================================================================
29.12.2025 - Gitea-Integration Bugfix: Branch-Mismatch
================================================================================
BUGFIX: DEFAULT-BRANCH MISMATCH ZWISCHEN GIT UND GITEA
--------------------------------------------------------------------------------
- Problem: Dateien wurden nach "master" gepusht, aber Gitea zeigte "main"
- Benutzer sahen nur die auto-generierte README statt ihrer gepushten Dateien
- Ursache: Lokaler Branch "master" != Gitea Default-Branch "main"
LÖSUNG
--------------------------------------------------------------------------------
- backend/services/giteaService.js: NEU - updateRepository() Funktion
* Erlaubt das Ändern des Default-Branch über Gitea PATCH API
* Unterstützt auch Beschreibung und Private-Status Änderungen
- backend/services/gitService.js: pushWithUpstream() gibt jetzt Branch-Namen zurück
- backend/routes/git.js: init-push Endpoint aktualisiert Default-Branch automatisch
* Nach erfolgreichem Push wird Gitea's Default-Branch auf den gepushten Branch gesetzt
* Owner/Repo wird automatisch aus der Repository-URL extrahiert
- frontend/sw.js: Cache-Version auf 119 erhöht
================================================================================ ================================================================================
28.12.2025 - Gitea-Integration 28.12.2025 - Gitea-Integration
================================================================================ ================================================================================

179
GITEA_INTEGRATION_STATUS.md Normale Datei
Datei anzeigen

@ -0,0 +1,179 @@
# Gitea-Integration Status - TaskMate
**Datum:** 29.12.2024
**Status:** Vollständig implementiert - Bugfix angewendet
---
## Übersicht
Die Gitea-Integration für TaskMate ist vollständig implementiert. Der kritische Branch-Mismatch Bug wurde behoben - nach einem Push wird der Default-Branch in Gitea automatisch auf den gepushten Branch aktualisiert.
---
## Was wurde bereits implementiert
### Backend (vollständig)
1. **`backend/server.js`** - Routes registriert:
```javascript
const gitRoutes = require('./routes/git');
const applicationsRoutes = require('./routes/applications');
const giteaRoutes = require('./routes/gitea');
app.use('/api/git', authenticateToken, csrfProtection, gitRoutes);
app.use('/api/applications', authenticateToken, csrfProtection, applicationsRoutes);
app.use('/api/gitea', authenticateToken, csrfProtection, giteaRoutes);
```
2. **`backend/routes/gitea.js`** - NEU erstellt mit Endpoints:
- `GET /test` - Verbindung testen
- `GET /repositories` - Alle Repos auflisten
- `POST /repositories` - Neues Repo erstellen
- `GET /repositories/:owner/:repo` - Repo-Details
- `GET /repositories/:owner/:repo/branches` - Branches
- `GET /repositories/:owner/:repo/commits` - Commits
3. **`backend/routes/git.js`** - Erweitert mit:
- `POST /set-remote` - Remote setzen
- `POST /prepare-for-gitea` - Repository für Gitea vorbereiten
4. **`backend/services/gitService.js`** - Erweitert mit:
- `setRemote()` - Remote-URL setzen
- `prepareForGitea()` - Repository für Gitea vorbereiten (remote + initial commit)
- `pushWithUpstream()` - Push mit automatischer Branch-Erkennung
5. **`backend/services/giteaService.js`** - Vorhanden mit:
- `listRepositories()` - Organisation-Repositories auflisten
- `getRepository()` - Einzelnes Repo abrufen
- `createRepository()` - Neues Repo erstellen
- `updateRepository()` - Repo-Einstellungen aktualisieren (inkl. default_branch)
- `deleteRepository()` - Repo löschen
- `testConnection()` - Verbindung testen
- `getAuthenticatedCloneUrl()` - Clone-URL mit Token
6. **`Dockerfile`** - Git-Unterstützung hinzugefügt:
```dockerfile
RUN apk add --no-cache git
RUN git config --system --add safe.directory '*'
RUN git config --system user.email "taskmate@local" && \
git config --system user.name "TaskMate" && \
git config --system init.defaultBranch main
```
### Frontend (vollständig)
1. **`frontend/js/api.js`** - Erweitert mit allen Gitea/Git API-Methoden
2. **`frontend/js/gitea.js`** - NEU erstellt:
- Kompletter GiteaManager mit allen Funktionen
- Konfigurationsansicht
- Status-Anzeige
- Git-Operationen (Fetch, Pull, Push, Commit)
- Commit-Historie
- Branch-Wechsel
3. **`frontend/css/gitea.css`** - NEU erstellt:
- Alle Styles für Gitea-Tab
4. **`frontend/index.html`** - Erweitert:
- Gitea-Tab in Navigation
- Gitea-View Container
- Commit-Modal
- Create-Repo-Modal
5. **`frontend/js/app.js`** - Integriert:
- GiteaManager Import
- View-Switching für Gitea-Tab
6. **`frontend/sw.js`** - Cache-Version erhöht
---
## Der gelöste Bug: Branch-Mismatch
### Problem (GELÖST)
- 146 Dateien wurden erfolgreich nach Gitea gepusht
- Die Dateien landeten im Branch `master`
- Gitea zeigte aber `main` als Standard-Branch an
- Benutzer sahen nur die auto-generierte README statt ihrer Dateien
### Ursache
1. Gitea erstellt Repositories mit `default_branch: 'main'` (Zeile 179 in giteaService.js)
2. Der lokale Git-Branch heißt aber `master`
3. Die Dateien wurden nach `master` gepusht
4. Gitea zeigte `main` an (leer bis auf README)
### Implementierte Lösung
1. **`updateRepository()` Funktion** zu `backend/services/giteaService.js` hinzugefügt
- Ändert den Default-Branch über Gitea PATCH API
- Unterstützt auch Beschreibung und Private-Status
2. **`pushWithUpstream()` Funktion** in `backend/services/gitService.js` erweitert
- Gibt jetzt den Branch-Namen im Ergebnis zurück
3. **`init-push` Endpoint** in `backend/routes/git.js` erweitert
- Nach erfolgreichem Push wird automatisch der Default-Branch in Gitea aktualisiert
- Owner/Repo wird aus der Repository-URL extrahiert
4. **Cache-Version** auf 119 erhöht in `frontend/sw.js`
---
## Gelöste Probleme (zur Referenz)
1. **"git: not found"** → Git in Dockerfile installiert
2. **"dubious ownership in repository"** → `safe.directory '*'` konfiguriert
3. **"No configured push destination"** → `setRemote()` und `prepareForGitea()` hinzugefügt
4. **"src refspec main does not match any"** → Branch-Erkennung in `pushWithUpstream()` implementiert
---
## Wichtige Dateien
| Datei | Status |
|-------|--------|
| `backend/server.js` | ✅ Fertig |
| `backend/routes/gitea.js` | ✅ Fertig |
| `backend/routes/git.js` | ✅ Fertig (inkl. auto Default-Branch Update) |
| `backend/services/gitService.js` | ✅ Fertig (inkl. Branch-Name Return) |
| `backend/services/giteaService.js` | ✅ Fertig (inkl. updateRepository) |
| `Dockerfile` | ✅ Fertig |
| `frontend/js/api.js` | ✅ Fertig |
| `frontend/js/gitea.js` | ✅ Fertig |
| `frontend/css/gitea.css` | ✅ Fertig |
| `frontend/index.html` | ✅ Fertig |
| `frontend/js/app.js` | ✅ Fertig |
| `frontend/sw.js` | ✅ Fertig (v119) |
---
## Nächste Schritte
1. ✅ `updateRepository()` zu `backend/services/giteaService.js` hinzugefügt
2. ✅ Funktion exportiert
3. ✅ Push-Flow erweitert, um Default-Branch nach Push zu aktualisieren
4. ✅ Docker-Container neu gebaut
5. ⏳ Testen: Projekt mit Gitea verknüpfen → Push → Dateien in Gitea sichtbar
---
## Plan-Datei
Der vollständige Implementierungsplan befindet sich in:
`C:\Users\hendr\.claude\plans\rosy-stargazing-scroll.md`
---
## Befehle zum Testen
```bash
# Container neu bauen
docker compose down
docker compose build --no-cache
docker compose up -d
# Browser öffnen
start chrome --incognito http://localhost:3000
```

Datei anzeigen

@ -175,8 +175,14 @@ router.post('/commit/:projectId', (req, res) => {
} }
} }
// Commit erstellen // Autor aus eingeloggtem Benutzer
const result = gitService.commit(application.local_path, message); const author = req.user ? {
name: req.user.display_name || req.user.username,
email: req.user.email || `${req.user.username.toLowerCase()}@taskmate.local`
} : null;
// Commit erstellen mit Autor
const result = gitService.commit(application.local_path, message, author);
res.json(result); res.json(result);
} catch (error) { } catch (error) {
logger.error('Fehler beim Commit:', error); logger.error('Fehler beim Commit:', error);
@ -414,24 +420,58 @@ router.post('/set-remote/:projectId', (req, res) => {
/** /**
* POST /api/git/init-push/:projectId * POST /api/git/init-push/:projectId
* Initialen Push mit Upstream-Tracking * 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', (req, res) => { router.post('/init-push/:projectId', async (req, res) => {
try { try {
const { projectId } = req.params; const { projectId } = req.params;
const { branch } = req.body; const { targetBranch, force } = req.body;
const application = getApplicationForProject(projectId); const application = getApplicationForProject(projectId);
if (!application) { if (!application) {
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
} }
const currentBranch = branch || 'main'; // targetBranch kann null sein - dann wird der lokale Branch-Name verwendet
const result = gitService.pushWithUpstream(application.local_path, currentBranch); // force: boolean - überschreibt Remote-Branch bei Konflikten
const result = gitService.pushWithUpstream(application.local_path, targetBranch || null, 'origin', force === true);
if (result.success) { if (result.success) {
// Sync-Zeitpunkt aktualisieren // Sync-Zeitpunkt aktualisieren
const db = getDb(); const db = getDb();
db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId); 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); res.json(result);
@ -441,4 +481,31 @@ router.post('/init-push/:projectId', (req, res) => {
} }
}); });
/**
* 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; module.exports = router;

Datei anzeigen

@ -253,8 +253,11 @@ function stageAll(localPath) {
/** /**
* Commit erstellen * Commit erstellen
* @param {string} localPath - Pfad zum Repository
* @param {string} message - Commit-Nachricht
* @param {object} author - Autor-Informationen { name, email }
*/ */
function commit(localPath, message) { function commit(localPath, message, author = null) {
if (!isGitRepository(localPath)) { if (!isGitRepository(localPath)) {
return { success: false, error: 'Kein Git-Repository' }; return { success: false, error: 'Kein Git-Repository' };
} }
@ -265,7 +268,17 @@ function commit(localPath, message) {
// Escape für Shell // Escape für Shell
const escapedMessage = message.replace(/"/g, '\\"'); const escapedMessage = message.replace(/"/g, '\\"');
return execGitCommand(`git commit -m "${escapedMessage}"`, localPath);
// Commit-Befehl mit optionalem Autor
let commitCmd = `git commit -m "${escapedMessage}"`;
if (author && author.name) {
const authorName = author.name.replace(/"/g, '\\"');
const authorEmail = author.email || `${author.name.toLowerCase().replace(/\s+/g, '.')}@taskmate.local`;
commitCmd = `git commit --author="${authorName} <${authorEmail}>" -m "${escapedMessage}"`;
logger.info(`Commit mit Autor: ${authorName} <${authorEmail}>`);
}
return execGitCommand(commitCmd, localPath);
} }
/** /**
@ -443,7 +456,7 @@ function hasRemote(localPath, remoteName = 'origin') {
/** /**
* Initialen Push mit Upstream-Tracking * Initialen Push mit Upstream-Tracking
*/ */
function pushWithUpstream(localPath, branch = null, remoteName = 'origin') { function pushWithUpstream(localPath, targetBranch = null, remoteName = 'origin', force = false) {
if (!isGitRepository(localPath)) { if (!isGitRepository(localPath)) {
return { success: false, error: 'Kein Git-Repository' }; return { success: false, error: 'Kein Git-Repository' };
} }
@ -477,11 +490,12 @@ function pushWithUpstream(localPath, branch = null, remoteName = 'origin') {
} }
} }
// Aktuellen Branch ermitteln NACH dem Commit // Aktuellen (lokalen) Branch ermitteln NACH dem Commit
let localBranch = null;
const branchResult = execGitCommand('git branch --show-current', localPath); const branchResult = execGitCommand('git branch --show-current', localPath);
if (branchResult.success && branchResult.output) { if (branchResult.success && branchResult.output) {
branch = branchResult.output; localBranch = branchResult.output;
logger.info(`Aktueller Branch: ${branch}`); logger.info(`Lokaler Branch: ${localBranch}`);
} else { } else {
// Fallback: Branch aus git branch auslesen // Fallback: Branch aus git branch auslesen
const branchListResult = execGitCommand('git branch', localPath); const branchListResult = execGitCommand('git branch', localPath);
@ -491,29 +505,46 @@ function pushWithUpstream(localPath, branch = null, remoteName = 'origin') {
const lines = branchListResult.output.split('\n'); const lines = branchListResult.output.split('\n');
for (const line of lines) { for (const line of lines) {
if (line.includes('*')) { if (line.includes('*')) {
branch = line.replace('*', '').trim(); localBranch = line.replace('*', '').trim();
break; break;
} }
} }
} }
if (!branch) { if (!localBranch) {
// Letzter Fallback: Versuche den Branch zu erstellen // Letzter Fallback: Versuche den Branch zu erstellen
logger.info('Kein Branch gefunden, erstelle main'); logger.info('Kein Branch gefunden, erstelle main');
execGitCommand('git checkout -b main', localPath); execGitCommand('git checkout -b main', localPath);
branch = 'main'; localBranch = 'main';
} }
} }
logger.info(`Push auf Branch: ${branch}`); // Wenn kein Ziel-Branch angegeben, verwende lokalen Branch-Namen
const remoteBranch = targetBranch || localBranch;
logger.info(`Push: lokaler Branch '${localBranch}' → Remote Branch '${remoteBranch}'${force ? ' (FORCE)' : ''}`);
// Push mit -u für Upstream-Tracking // Push mit -u für Upstream-Tracking
const pushResult = execGitCommand(`git push -u ${remoteName} ${branch}`, localPath, { timeout: 120000 }); // Format: git push -u origin localBranch:remoteBranch
const forceFlag = force ? ' --force' : '';
let pushCommand;
if (localBranch === remoteBranch) {
pushCommand = `git push -u${forceFlag} ${remoteName} ${localBranch}`;
} else {
// Branch-Mapping: lokalen Branch auf anderen Remote-Branch pushen
pushCommand = `git push -u${forceFlag} ${remoteName} ${localBranch}:${remoteBranch}`;
}
const pushResult = execGitCommand(pushCommand, localPath, { timeout: 120000 });
if (!pushResult.success) { if (!pushResult.success) {
logger.error(`Push fehlgeschlagen: ${pushResult.error}`); logger.error(`Push fehlgeschlagen: ${pushResult.error}`);
} }
// Branch-Namen im Ergebnis inkludieren für Default-Branch Aktualisierung
pushResult.localBranch = localBranch;
pushResult.branch = remoteBranch; // Remote-Branch für Gitea Default-Branch
return pushResult; return pushResult;
} }
@ -561,6 +592,39 @@ function prepareForGitea(localPath, remoteUrl, options = {}) {
return { success: true, message: 'Repository für Gitea vorbereitet' }; return { success: true, message: 'Repository für Gitea vorbereitet' };
} }
/**
* Branch umbenennen
*/
function renameBranch(localPath, oldName, newName) {
if (!isGitRepository(localPath)) {
return { success: false, error: 'Kein Git-Repository' };
}
// Validiere neuen Branch-Namen
if (!newName || !/^[a-zA-Z0-9_\-]+$/.test(newName)) {
return { success: false, error: 'Ungültiger Branch-Name. Nur Buchstaben, Zahlen, Unterstriche und Bindestriche erlaubt.' };
}
// Prüfe ob wir auf dem zu umbenennenden Branch sind
const branchResult = execGitCommand('git branch --show-current', localPath);
const currentBranch = branchResult.success ? branchResult.output : null;
if (currentBranch !== oldName) {
return { success: false, error: `Bitte wechsle zuerst zum Branch "${oldName}" bevor du ihn umbenennst.` };
}
// Branch umbenennen
const renameResult = execGitCommand(`git branch -m ${oldName} ${newName}`, localPath);
if (!renameResult.success) {
logger.error(`Branch umbenennen fehlgeschlagen: ${renameResult.error}`);
return renameResult;
}
logger.info(`Branch umbenannt: ${oldName}${newName}`);
return { success: true, oldName, newName, message: `Branch "${oldName}" zu "${newName}" umbenannt` };
}
module.exports = { module.exports = {
windowsToContainerPath, windowsToContainerPath,
containerToWindowsPath, containerToWindowsPath,
@ -581,5 +645,6 @@ module.exports = {
setRemote, setRemote,
hasRemote, hasRemote,
pushWithUpstream, pushWithUpstream,
prepareForGitea prepareForGitea,
renameBranch
}; };

Datei anzeigen

@ -284,12 +284,44 @@ function getSafeCloneUrl(cloneUrl) {
return cloneUrl.replace(/https:\/\/[^@]+@/, 'https://'); return cloneUrl.replace(/https:\/\/[^@]+@/, 'https://');
} }
/**
* Repository-Einstellungen aktualisieren (z.B. default_branch ändern)
*/
async function updateRepository(owner, repo, options = {}) {
try {
const updateData = {};
if (options.defaultBranch) updateData.default_branch = options.defaultBranch;
if (options.description !== undefined) updateData.description = options.description;
if (options.private !== undefined) updateData.private = options.private;
const repoData = await giteaFetch(`/repos/${owner}/${repo}`, {
method: 'PATCH',
body: JSON.stringify(updateData)
});
logger.info(`Repository ${owner}/${repo} aktualisiert (default_branch: ${repoData.default_branch})`);
return {
success: true,
repository: {
id: repoData.id,
name: repoData.name,
fullName: repoData.full_name,
defaultBranch: repoData.default_branch
}
};
} catch (error) {
logger.error(`Fehler beim Aktualisieren des Repositories ${owner}/${repo}:`, error);
return { success: false, error: error.message };
}
}
module.exports = { module.exports = {
listRepositories, listRepositories,
getRepository, getRepository,
getRepositoryBranches, getRepositoryBranches,
getRepositoryCommits, getRepositoryCommits,
createRepository, createRepository,
updateRepository,
deleteRepository, deleteRepository,
getCurrentUser, getCurrentUser,
testConnection, testConnection,

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -225,6 +225,12 @@
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
.branch-select-group {
display: flex;
align-items: center;
gap: var(--spacing-2);
}
.branch-select { .branch-select {
padding: var(--spacing-2) var(--spacing-3); padding: var(--spacing-2) var(--spacing-3);
border-radius: var(--radius-md); border-radius: var(--radius-md);
@ -233,6 +239,7 @@
color: var(--text-primary); color: var(--text-primary);
font-size: var(--text-sm); font-size: var(--text-sm);
cursor: pointer; cursor: pointer;
flex: 1;
} }
.branch-select:focus { .branch-select:focus {
@ -240,6 +247,24 @@
border-color: var(--primary); border-color: var(--primary);
} }
.btn-small {
padding: var(--spacing-1) var(--spacing-2);
font-size: var(--text-xs);
}
.btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--spacing-1);
min-width: 28px;
min-height: 28px;
}
.btn-icon svg {
flex-shrink: 0;
}
.status-badge { .status-badge {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -259,11 +284,6 @@
color: var(--warning); color: var(--warning);
} }
.status-badge.ahead {
background: rgba(59, 130, 246, 0.1);
color: var(--info);
}
.status-badge.error { .status-badge.error {
background: rgba(239, 68, 68, 0.1); background: rgba(239, 68, 68, 0.1);
color: var(--error); color: var(--error);
@ -275,12 +295,6 @@
color: var(--text-primary); color: var(--text-primary);
} }
#git-ahead-behind {
font-size: var(--text-sm);
color: var(--text-primary);
font-family: monospace;
}
/* ============================================================================= /* =============================================================================
OPERATIONS PANEL OPERATIONS PANEL
============================================================================= */ ============================================================================= */
@ -511,6 +525,20 @@
color: var(--error); color: var(--error);
} }
.form-hint.warning {
color: var(--warning);
}
.form-static-value {
padding: var(--spacing-2) var(--spacing-3);
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.checkbox-label { .checkbox-label {
display: flex; display: flex;
align-items: center; align-items: center;

Datei anzeigen

@ -606,9 +606,14 @@
<div class="status-grid"> <div class="status-grid">
<div class="status-item"> <div class="status-item">
<span class="status-label">Branch</span> <span class="status-label">Branch</span>
<select id="branch-select" class="branch-select"> <div class="branch-select-group">
<!-- Branches dynamisch --> <select id="branch-select" class="branch-select">
</select> <!-- Branches dynamisch -->
</select>
<button id="btn-rename-branch" class="btn btn-small btn-icon" title="Branch umbenennen">
<svg viewBox="0 0 24 24" width="16" height="16"><path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" stroke="currentColor" stroke-width="2" fill="none"/></svg>
</button>
</div>
</div> </div>
<div class="status-item"> <div class="status-item">
<span class="status-label">Status</span> <span class="status-label">Status</span>
@ -618,10 +623,6 @@
<span class="status-label">Änderungen</span> <span class="status-label">Änderungen</span>
<span id="git-changes-count" class="changes-count">0</span> <span id="git-changes-count" class="changes-count">0</span>
</div> </div>
<div class="status-item">
<span class="status-label">Ahead / Behind</span>
<span id="git-ahead-behind">- / -</span>
</div>
</div> </div>
</div> </div>
@ -1163,6 +1164,70 @@
</div> </div>
</div> </div>
<!-- Git Branch Rename Modal -->
<div id="git-rename-branch-modal" class="modal modal-small hidden">
<div class="modal-header">
<h2>Branch umbenennen</h2>
<button class="modal-close" data-close-modal>&times;</button>
</div>
<div class="modal-body">
<form id="git-rename-branch-form">
<div class="form-group">
<label>Aktueller Branch</label>
<div id="rename-current-branch" class="form-static-value">master</div>
</div>
<div class="form-group">
<label for="rename-new-branch">Neuer Name *</label>
<input type="text" id="rename-new-branch" required
placeholder="z.B. main" pattern="[a-zA-Z0-9_\-]+"
title="Nur Buchstaben, Zahlen, Unterstriche und Bindestriche">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
<button type="submit" form="git-rename-branch-form" class="btn btn-primary">Umbenennen</button>
</div>
</div>
<!-- Git Push Modal -->
<div id="git-push-modal" class="modal modal-small hidden">
<div class="modal-header">
<h2>Push zu Gitea</h2>
<button class="modal-close" data-close-modal>&times;</button>
</div>
<div class="modal-body">
<form id="git-push-form">
<div class="form-group">
<label>Lokaler Branch</label>
<div id="push-local-branch" class="form-static-value">wird ermittelt...</div>
<span class="form-hint">Der aktuelle lokale Branch wird automatisch erkannt</span>
</div>
<div class="form-group">
<label for="push-target-branch">Ziel-Branch auf Remote</label>
<select id="push-target-branch">
<option value="">Gleicher Name wie lokal</option>
<option value="main">main</option>
<option value="master">master</option>
<option value="develop">develop</option>
</select>
<span class="form-hint">Wählen Sie den Branch-Namen auf Gitea</span>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="push-force">
Force Push (Remote überschreiben)
</label>
<span class="form-hint warning">Achtung: Überschreibt alle Änderungen auf dem Remote-Branch!</span>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-close-modal>Abbrechen</button>
<button type="submit" form="git-push-form" class="btn btn-primary">Push starten</button>
</div>
</div>
<!-- Create Repository Modal --> <!-- Create Repository Modal -->
<div id="create-repo-modal" class="modal modal-small hidden"> <div id="create-repo-modal" class="modal modal-small hidden">
<div class="modal-header"> <div class="modal-header">

Datei anzeigen

@ -820,8 +820,12 @@ class ApiClient {
return this.post(`/git/set-remote/${projectId}`, { repoUrl }); return this.post(`/git/set-remote/${projectId}`, { repoUrl });
} }
async gitInitPush(projectId, branch = 'main') { async gitInitPush(projectId, targetBranch = null, force = false) {
return this.post(`/git/init-push/${projectId}`, { branch }); return this.post(`/git/init-push/${projectId}`, { targetBranch, force });
}
async gitRenameBranch(projectId, oldName, newName) {
return this.post(`/git/rename-branch/${projectId}`, { oldName, newName });
} }
} }

Datei anzeigen

@ -72,6 +72,13 @@ class GiteaManager {
// Commit Modal // Commit Modal
$('#git-commit-form')?.addEventListener('submit', (e) => this.handleCommit(e)); $('#git-commit-form')?.addEventListener('submit', (e) => this.handleCommit(e));
// Push Modal
$('#git-push-form')?.addEventListener('submit', (e) => this.executePush(e));
// Branch umbenennen
$('#btn-rename-branch')?.addEventListener('click', () => this.openRenameBranchModal());
$('#git-rename-branch-form')?.addEventListener('submit', (e) => this.executeRenameBranch(e));
} }
subscribeToStore() { subscribeToStore() {
@ -406,27 +413,76 @@ class GiteaManager {
} }
} }
async handlePush() { handlePush() {
// Öffne das Push-Modal zur Branch-Auswahl
this.openPushModal();
}
openPushModal() {
const modal = $('#git-push-modal');
if (!modal) return;
// Aktuellen lokalen Branch anzeigen
const currentBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'unbekannt';
$('#push-local-branch').textContent = currentBranch;
// Ziel-Branch zurücksetzen (Standard: gleicher Name wie lokal)
$('#push-target-branch').value = '';
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('git-push-modal');
}
async executePush(e) {
e.preventDefault();
const projectId = store.get('currentProjectId'); const projectId = store.get('currentProjectId');
if (!projectId) return; if (!projectId) return;
// Modal schließen
const modal = $('#git-push-modal');
modal?.classList.add('hidden');
modal?.classList.remove('visible');
$('#modal-overlay')?.classList.add('hidden');
store.closeModal();
this.setOperationLoading('push', true); this.setOperationLoading('push', true);
try { try {
const branch = $('#branch-select')?.value || 'main'; const targetBranch = $('#push-target-branch')?.value || null;
let result = await api.gitPush(projectId, branch); const forcePush = $('#push-force')?.checked || false;
const localBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'master';
// Falls Push wegen fehlendem Upstream/Remote fehlschlägt, versuche init-push let result;
if (!result.success && result.error &&
(result.error.includes('No configured push destination') || // Wenn ein anderer Target-Branch ausgewählt wurde oder Force-Push, verwende init-push
result.error.includes('no upstream') || if ((targetBranch && targetBranch !== localBranch) || forcePush) {
result.error.includes('Kein Remote'))) { const forceText = forcePush ? ' (Force)' : '';
this.showToast('Kein Upstream konfiguriert, führe initialen Push durch...', 'info'); const branchText = targetBranch && targetBranch !== localBranch
result = await api.gitInitPush(projectId, branch); ? `Push von "${localBranch}" nach "${targetBranch}"${forceText}...`
: `Push nach "${localBranch}"${forceText}...`;
this.showToast(branchText, 'info');
result = await api.gitInitPush(projectId, targetBranch, forcePush);
} else {
// Normaler Push (gleicher Branch-Name)
result = await api.gitPush(projectId, localBranch);
// Falls Push wegen fehlendem Upstream/Remote fehlschlägt, versuche init-push
if (!result.success && result.error &&
(result.error.includes('No configured push destination') ||
result.error.includes('no upstream') ||
result.error.includes('Kein Remote'))) {
this.showToast('Kein Upstream konfiguriert, führe initialen Push durch...', 'info');
result = await api.gitInitPush(projectId, targetBranch, false);
}
} }
if (result.success) { if (result.success) {
this.showToast('Push erfolgreich', 'success'); const pushedBranch = result.branch || targetBranch || localBranch;
this.showToast(`Push erfolgreich nach Branch "${pushedBranch}"`, 'success');
await this.loadGitData(); await this.loadGitData();
} else { } else {
this.showToast(result.error || 'Push fehlgeschlagen', 'error'); this.showToast(result.error || 'Push fehlgeschlagen', 'error');
@ -438,6 +494,64 @@ class GiteaManager {
} }
} }
openRenameBranchModal() {
const modal = $('#git-rename-branch-modal');
if (!modal) return;
const currentBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'master';
$('#rename-current-branch').textContent = currentBranch;
$('#rename-new-branch').value = '';
modal.classList.remove('hidden');
modal.classList.add('visible');
$('#modal-overlay')?.classList.remove('hidden');
store.openModal('git-rename-branch-modal');
// Focus auf das Eingabefeld
setTimeout(() => $('#rename-new-branch')?.focus(), 100);
}
async executeRenameBranch(e) {
e.preventDefault();
const projectId = store.get('currentProjectId');
if (!projectId) return;
const oldName = $('#rename-current-branch')?.textContent;
const newName = $('#rename-new-branch')?.value.trim();
if (!newName) {
this.showToast('Bitte geben Sie einen neuen Branch-Namen ein', 'error');
return;
}
if (oldName === newName) {
this.showToast('Der neue Name ist identisch mit dem aktuellen', 'error');
return;
}
// Modal schließen
const modal = $('#git-rename-branch-modal');
modal?.classList.add('hidden');
modal?.classList.remove('visible');
$('#modal-overlay')?.classList.add('hidden');
store.closeModal();
try {
const result = await api.gitRenameBranch(projectId, oldName, newName);
if (result.success) {
this.showToast(`Branch umbenannt: "${oldName}" → "${newName}"`, 'success');
await this.loadGitData();
} else {
this.showToast(result.error || 'Umbenennen fehlgeschlagen', 'error');
}
} catch (error) {
this.showToast(error.message || 'Umbenennen fehlgeschlagen', 'error');
}
}
openCommitModal() { openCommitModal() {
const modal = $('#git-commit-modal'); const modal = $('#git-commit-modal');
if (!modal) return; if (!modal) return;
@ -610,13 +724,11 @@ class GiteaManager {
renderStatus() { renderStatus() {
const statusBadge = $('#git-status-indicator'); const statusBadge = $('#git-status-indicator');
const changesCount = $('#git-changes-count'); const changesCount = $('#git-changes-count');
const aheadBehind = $('#git-ahead-behind');
if (!this.gitStatus) { if (!this.gitStatus) {
statusBadge.textContent = 'Fehler'; statusBadge.textContent = 'Fehler';
statusBadge.className = 'status-badge error'; statusBadge.className = 'status-badge error';
changesCount.textContent = '-'; changesCount.textContent = '-';
aheadBehind.textContent = '- / -';
return; return;
} }
@ -630,9 +742,6 @@ class GiteaManager {
} else if (this.gitStatus.hasChanges) { } else if (this.gitStatus.hasChanges) {
statusBadge.textContent = 'Geändert'; statusBadge.textContent = 'Geändert';
statusBadge.className = 'status-badge dirty'; statusBadge.className = 'status-badge dirty';
} else if (this.gitStatus.ahead > 0) {
statusBadge.textContent = 'Voraus';
statusBadge.className = 'status-badge ahead';
} else { } else {
statusBadge.textContent = 'OK'; statusBadge.textContent = 'OK';
statusBadge.className = 'status-badge clean'; statusBadge.className = 'status-badge clean';
@ -641,11 +750,6 @@ class GiteaManager {
// Änderungen // Änderungen
const changes = this.gitStatus.changes || []; const changes = this.gitStatus.changes || [];
changesCount.textContent = changes.length; changesCount.textContent = changes.length;
// Ahead/Behind
const ahead = this.gitStatus.ahead || 0;
const behind = this.gitStatus.behind || 0;
aheadBehind.textContent = `${ahead} / ${behind}`;
} }
renderBranches() { renderBranches() {

Datei anzeigen

@ -4,7 +4,7 @@
* Offline support and caching * Offline support and caching
*/ */
const CACHE_VERSION = '118'; const CACHE_VERSION = '124';
const CACHE_NAME = 'taskmate-v' + CACHE_VERSION; const CACHE_NAME = 'taskmate-v' + CACHE_VERSION;
const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION; const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION;
const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION; const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;

Datei-Diff unterdrückt, da er zu groß ist Diff laden