Update Gitea-Sektion:
Branch-Auswahl
Dieser Commit ist enthalten in:
117
CHANGELOG.txt
117
CHANGELOG.txt
@ -1,6 +1,123 @@
|
||||
TASKMATE - CHANGELOG
|
||||
====================
|
||||
|
||||
================================================================================
|
||||
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
|
||||
================================================================================
|
||||
|
||||
179
GITEA_INTEGRATION_STATUS.md
Normale Datei
179
GITEA_INTEGRATION_STATUS.md
Normale Datei
@ -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
|
||||
```
|
||||
@ -414,24 +414,58 @@ router.post('/set-remote/:projectId', (req, res) => {
|
||||
/**
|
||||
* 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', (req, res) => {
|
||||
router.post('/init-push/:projectId', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const { branch } = req.body;
|
||||
const { targetBranch, force } = req.body;
|
||||
const application = getApplicationForProject(projectId);
|
||||
|
||||
if (!application) {
|
||||
return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' });
|
||||
}
|
||||
|
||||
const currentBranch = branch || 'main';
|
||||
const result = gitService.pushWithUpstream(application.local_path, currentBranch);
|
||||
// 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);
|
||||
@ -441,4 +475,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;
|
||||
|
||||
@ -443,7 +443,7 @@ function hasRemote(localPath, remoteName = 'origin') {
|
||||
/**
|
||||
* Initialen Push mit Upstream-Tracking
|
||||
*/
|
||||
function pushWithUpstream(localPath, branch = null, remoteName = 'origin') {
|
||||
function pushWithUpstream(localPath, targetBranch = null, remoteName = 'origin', force = false) {
|
||||
if (!isGitRepository(localPath)) {
|
||||
return { success: false, error: 'Kein Git-Repository' };
|
||||
}
|
||||
@ -477,11 +477,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);
|
||||
if (branchResult.success && branchResult.output) {
|
||||
branch = branchResult.output;
|
||||
logger.info(`Aktueller Branch: ${branch}`);
|
||||
localBranch = branchResult.output;
|
||||
logger.info(`Lokaler Branch: ${localBranch}`);
|
||||
} else {
|
||||
// Fallback: Branch aus git branch auslesen
|
||||
const branchListResult = execGitCommand('git branch', localPath);
|
||||
@ -491,29 +492,46 @@ function pushWithUpstream(localPath, branch = null, remoteName = 'origin') {
|
||||
const lines = branchListResult.output.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.includes('*')) {
|
||||
branch = line.replace('*', '').trim();
|
||||
localBranch = line.replace('*', '').trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!branch) {
|
||||
if (!localBranch) {
|
||||
// Letzter Fallback: Versuche den Branch zu erstellen
|
||||
logger.info('Kein Branch gefunden, erstelle main');
|
||||
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
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -561,6 +579,39 @@ function prepareForGitea(localPath, remoteUrl, options = {}) {
|
||||
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 = {
|
||||
windowsToContainerPath,
|
||||
containerToWindowsPath,
|
||||
@ -581,5 +632,6 @@ module.exports = {
|
||||
setRemote,
|
||||
hasRemote,
|
||||
pushWithUpstream,
|
||||
prepareForGitea
|
||||
prepareForGitea,
|
||||
renameBranch
|
||||
};
|
||||
|
||||
@ -284,12 +284,44 @@ function getSafeCloneUrl(cloneUrl) {
|
||||
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 = {
|
||||
listRepositories,
|
||||
getRepository,
|
||||
getRepositoryBranches,
|
||||
getRepositoryCommits,
|
||||
createRepository,
|
||||
updateRepository,
|
||||
deleteRepository,
|
||||
getCurrentUser,
|
||||
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.
0
backups/backup_2025-12-29T18-03-28-634Z.db-wal
Normale Datei
0
backups/backup_2025-12-29T18-03-28-634Z.db-wal
Normale Datei
Binäre Datei nicht angezeigt.
0
backups/backup_2025-12-29T18-30-58-187Z.db-wal
Normale Datei
0
backups/backup_2025-12-29T18-30-58-187Z.db-wal
Normale Datei
Binäre Datei nicht angezeigt.
0
backups/backup_2025-12-29T18-36-12-724Z.db-wal
Normale Datei
0
backups/backup_2025-12-29T18-36-12-724Z.db-wal
Normale Datei
Binäre Datei nicht angezeigt.
0
backups/backup_2025-12-29T18-46-49-826Z.db-wal
Normale Datei
0
backups/backup_2025-12-29T18-46-49-826Z.db-wal
Normale Datei
BIN
backups/backup_2025-12-29T19-01-37-753Z.db
Normale Datei
BIN
backups/backup_2025-12-29T19-01-37-753Z.db
Normale Datei
Binäre Datei nicht angezeigt.
BIN
backups/backup_2025-12-29T19-01-37-753Z.db-wal
Normale Datei
BIN
backups/backup_2025-12-29T19-01-37-753Z.db-wal
Normale Datei
Binäre Datei nicht angezeigt.
BIN
data/taskmate.db
BIN
data/taskmate.db
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
@ -225,6 +225,12 @@
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.branch-select-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.branch-select {
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
border-radius: var(--radius-md);
|
||||
@ -233,6 +239,7 @@
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.branch-select:focus {
|
||||
@ -240,6 +247,24 @@
|
||||
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 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -259,11 +284,6 @@
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.status-badge.ahead {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--info);
|
||||
}
|
||||
|
||||
.status-badge.error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--error);
|
||||
@ -275,12 +295,6 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
#git-ahead-behind {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
OPERATIONS PANEL
|
||||
============================================================================= */
|
||||
@ -511,6 +525,20 @@
|
||||
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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -606,9 +606,14 @@
|
||||
<div class="status-grid">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Branch</span>
|
||||
<select id="branch-select" class="branch-select">
|
||||
<!-- Branches dynamisch -->
|
||||
</select>
|
||||
<div class="branch-select-group">
|
||||
<select id="branch-select" class="branch-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 class="status-item">
|
||||
<span class="status-label">Status</span>
|
||||
@ -618,10 +623,6 @@
|
||||
<span class="status-label">Änderungen</span>
|
||||
<span id="git-changes-count" class="changes-count">0</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Ahead / Behind</span>
|
||||
<span id="git-ahead-behind">- / -</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1163,6 +1164,70 @@
|
||||
</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>×</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>×</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 -->
|
||||
<div id="create-repo-modal" class="modal modal-small hidden">
|
||||
<div class="modal-header">
|
||||
|
||||
@ -820,8 +820,12 @@ class ApiClient {
|
||||
return this.post(`/git/set-remote/${projectId}`, { repoUrl });
|
||||
}
|
||||
|
||||
async gitInitPush(projectId, branch = 'main') {
|
||||
return this.post(`/git/init-push/${projectId}`, { branch });
|
||||
async gitInitPush(projectId, targetBranch = null, force = false) {
|
||||
return this.post(`/git/init-push/${projectId}`, { targetBranch, force });
|
||||
}
|
||||
|
||||
async gitRenameBranch(projectId, oldName, newName) {
|
||||
return this.post(`/git/rename-branch/${projectId}`, { oldName, newName });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -72,6 +72,13 @@ class GiteaManager {
|
||||
|
||||
// Commit Modal
|
||||
$('#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() {
|
||||
@ -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');
|
||||
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);
|
||||
|
||||
try {
|
||||
const branch = $('#branch-select')?.value || 'main';
|
||||
let result = await api.gitPush(projectId, branch);
|
||||
const targetBranch = $('#push-target-branch')?.value || null;
|
||||
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
|
||||
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, branch);
|
||||
let result;
|
||||
|
||||
// Wenn ein anderer Target-Branch ausgewählt wurde oder Force-Push, verwende init-push
|
||||
if ((targetBranch && targetBranch !== localBranch) || forcePush) {
|
||||
const forceText = forcePush ? ' (Force)' : '';
|
||||
const branchText = targetBranch && targetBranch !== localBranch
|
||||
? `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) {
|
||||
this.showToast('Push erfolgreich', 'success');
|
||||
const pushedBranch = result.branch || targetBranch || localBranch;
|
||||
this.showToast(`Push erfolgreich nach Branch "${pushedBranch}"`, 'success');
|
||||
await this.loadGitData();
|
||||
} else {
|
||||
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() {
|
||||
const modal = $('#git-commit-modal');
|
||||
if (!modal) return;
|
||||
@ -610,13 +724,11 @@ class GiteaManager {
|
||||
renderStatus() {
|
||||
const statusBadge = $('#git-status-indicator');
|
||||
const changesCount = $('#git-changes-count');
|
||||
const aheadBehind = $('#git-ahead-behind');
|
||||
|
||||
if (!this.gitStatus) {
|
||||
statusBadge.textContent = 'Fehler';
|
||||
statusBadge.className = 'status-badge error';
|
||||
changesCount.textContent = '-';
|
||||
aheadBehind.textContent = '- / -';
|
||||
return;
|
||||
}
|
||||
|
||||
@ -630,9 +742,6 @@ class GiteaManager {
|
||||
} else if (this.gitStatus.hasChanges) {
|
||||
statusBadge.textContent = 'Geändert';
|
||||
statusBadge.className = 'status-badge dirty';
|
||||
} else if (this.gitStatus.ahead > 0) {
|
||||
statusBadge.textContent = 'Voraus';
|
||||
statusBadge.className = 'status-badge ahead';
|
||||
} else {
|
||||
statusBadge.textContent = 'OK';
|
||||
statusBadge.className = 'status-badge clean';
|
||||
@ -641,11 +750,6 @@ class GiteaManager {
|
||||
// Änderungen
|
||||
const changes = this.gitStatus.changes || [];
|
||||
changesCount.textContent = changes.length;
|
||||
|
||||
// Ahead/Behind
|
||||
const ahead = this.gitStatus.ahead || 0;
|
||||
const behind = this.gitStatus.behind || 0;
|
||||
aheadBehind.textContent = `${ahead} / ${behind}`;
|
||||
}
|
||||
|
||||
renderBranches() {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Offline support and caching
|
||||
*/
|
||||
|
||||
const CACHE_VERSION = '118';
|
||||
const CACHE_VERSION = '123';
|
||||
const CACHE_NAME = 'taskmate-v' + CACHE_VERSION;
|
||||
const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION;
|
||||
const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;
|
||||
|
||||
3255
logs/app.log
3255
logs/app.log
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
In neuem Issue referenzieren
Einen Benutzer sperren