Update Gitea-Sektion:

Branch-Auswahl
Dieser Commit ist enthalten in:
Claude Project Manager
2025-12-29 19:02:44 +00:00
Ursprung 8da6ad83f0
Commit 627beb1353
30 geänderte Dateien mit 3953 neuen und 56 gelöschten Zeilen

Datei anzeigen

@ -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
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

@ -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;

Datei anzeigen

@ -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
};

Datei anzeigen

@ -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.

Datei anzeigen

@ -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;

Datei anzeigen

@ -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>&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 -->
<div id="create-repo-modal" class="modal modal-small hidden">
<div class="modal-header">

Datei anzeigen

@ -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 });
}
}

Datei anzeigen

@ -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() {

Datei anzeigen

@ -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;

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