540 Zeilen
23 KiB
Markdown
540 Zeilen
23 KiB
Markdown
# Duplicate Methods Analysis Report - gui/main_window.py
|
|
|
|
## Overview
|
|
This report analyzes the duplicate methods found in `gui/main_window.py` to determine their differences and whether they can be safely consolidated.
|
|
|
|
## 1. `manage_branches` (line 1374 vs line 2336)
|
|
|
|
### First Version (line 1374-1377)
|
|
```python
|
|
def manage_branches(self, project):
|
|
"""Manage git branches"""
|
|
messagebox.showinfo("Branches", "Branch management coming soon")
|
|
```
|
|
|
|
### Second Version (line 2336-2391)
|
|
```python
|
|
def manage_branches(self, project):
|
|
"""Manage branches for a project"""
|
|
from tkinter import simpledialog
|
|
|
|
git_ops = self.repo_manager.git_ops
|
|
|
|
# Get current branches
|
|
success, branches = git_ops.branch(Path(project.path), list_all=True)
|
|
|
|
if success:
|
|
current_branch = None
|
|
branch_list = []
|
|
for line in branches.strip().split('\n'):
|
|
if line.startswith('*'):
|
|
current_branch = line[2:].strip()
|
|
branch_list.append(f"{line.strip()} (aktuell)")
|
|
else:
|
|
branch_list.append(line.strip())
|
|
|
|
# Show options
|
|
action = messagebox.askyesnocancel(
|
|
"Branch-Verwaltung",
|
|
f"Aktuelle Branches:\n{chr(10).join(branch_list)}\n\n"
|
|
"Ja = Neuen Branch erstellen\n"
|
|
"Nein = Zu anderem Branch wechseln\n"
|
|
"Abbrechen = Schließen"
|
|
)
|
|
|
|
if action is True: # Create new branch
|
|
branch_name = simpledialog.askstring(
|
|
"Neuer Branch",
|
|
"Name des neuen Branches:",
|
|
parent=self.root
|
|
)
|
|
if branch_name:
|
|
success, result = git_ops.checkout(Path(project.path), branch_name, create=True)
|
|
if success:
|
|
messagebox.showinfo("Erfolg", f"Branch '{branch_name}' erstellt und gewechselt")
|
|
else:
|
|
messagebox.showerror("Fehler", f"Branch-Erstellung fehlgeschlagen: {result}")
|
|
|
|
elif action is False: # Switch branch
|
|
branch_name = simpledialog.askstring(
|
|
"Branch wechseln",
|
|
"Zu welchem Branch wechseln?",
|
|
parent=self.root
|
|
)
|
|
if branch_name:
|
|
success, result = git_ops.checkout(Path(project.path), branch_name)
|
|
if success:
|
|
messagebox.showinfo("Erfolg", f"Zu Branch '{branch_name}' gewechselt")
|
|
else:
|
|
messagebox.showerror("Fehler", f"Branch-Wechsel fehlgeschlagen: {result}")
|
|
else:
|
|
messagebox.showerror("Fehler", f"Konnte Branches nicht abrufen: {branches}")
|
|
```
|
|
|
|
### Differences
|
|
- **First version**: Placeholder implementation showing "coming soon" message
|
|
- **Second version**: Full implementation with branch listing, creation, and switching functionality
|
|
- **Language**: First version uses English, second version uses German
|
|
|
|
### Called From
|
|
- Line 1516: `self.manage_branches(project)` in `gitea_operation` method
|
|
|
|
### Recommendation
|
|
**Safe to consolidate**: Keep the second version (line 2336) as it contains the actual implementation. The first version is just a placeholder.
|
|
|
|
---
|
|
|
|
## 2. `link_to_gitea` (line 1378 vs line 2392)
|
|
|
|
### First Version (line 1378-1381)
|
|
```python
|
|
def link_to_gitea(self, project):
|
|
"""Link local project to Gitea repository"""
|
|
messagebox.showinfo("Link", "Link functionality coming soon")
|
|
```
|
|
|
|
### Second Version (line 2392-2456)
|
|
```python
|
|
def link_to_gitea(self, project):
|
|
"""Link local project to Gitea repository"""
|
|
from tkinter import simpledialog
|
|
|
|
# Ask for repository name
|
|
repo_name = simpledialog.askstring(
|
|
"Mit Gitea verknüpfen",
|
|
"Name des Gitea-Repositories:",
|
|
initialvalue=project.name,
|
|
parent=self.root
|
|
)
|
|
|
|
if repo_name:
|
|
git_ops = self.repo_manager.git_ops
|
|
|
|
# Check if repo exists on Gitea
|
|
try:
|
|
repo = self.repo_manager.client.get_repository(
|
|
self.repo_manager.client.config.username,
|
|
repo_name
|
|
)
|
|
|
|
# Add remote
|
|
success, msg = git_ops.add_remote_to_existing_repo(
|
|
Path(project.path),
|
|
self.repo_manager.client.config.username,
|
|
repo_name
|
|
)
|
|
|
|
if success:
|
|
# Update project with Gitea repo reference
|
|
project.gitea_repo = f"{self.repo_manager.client.config.username}/{repo_name}"
|
|
self.project_manager.update_project(project.id, gitea_repo=project.gitea_repo)
|
|
messagebox.showinfo("Erfolg", f"Erfolgreich mit Repository '{repo_name}' verknüpft!")
|
|
else:
|
|
messagebox.showerror("Fehler", f"Verknüpfung fehlgeschlagen: {msg}")
|
|
|
|
except Exception as e:
|
|
# Repository doesn't exist, offer to create
|
|
create = messagebox.askyesno(
|
|
"Repository nicht gefunden",
|
|
f"Repository '{repo_name}' existiert nicht.\n\nMöchten Sie es erstellen?"
|
|
)
|
|
|
|
if create:
|
|
try:
|
|
# Create repository
|
|
new_repo = self.repo_manager.create_repository(repo_name, auto_init=False)
|
|
|
|
# Add remote
|
|
success, msg = git_ops.add_remote_to_existing_repo(
|
|
Path(project.path),
|
|
self.repo_manager.client.config.username,
|
|
repo_name
|
|
)
|
|
|
|
if success:
|
|
# Update project
|
|
project.gitea_repo = f"{self.repo_manager.client.config.username}/{repo_name}"
|
|
self.project_manager.update_project(project.id, gitea_repo=project.gitea_repo)
|
|
messagebox.showinfo("Erfolg",
|
|
f"Repository '{repo_name}' erstellt und verknüpft!")
|
|
else:
|
|
messagebox.showerror("Fehler", f"Verknüpfung fehlgeschlagen: {msg}")
|
|
|
|
except Exception as create_error:
|
|
messagebox.showerror("Fehler",
|
|
f"Repository-Erstellung fehlgeschlagen: {str(create_error)}")
|
|
```
|
|
|
|
### Differences
|
|
- **First version**: Placeholder implementation showing "coming soon" message
|
|
- **Second version**: Full implementation with repository linking, checking existence, and creation option
|
|
- **Language**: First version uses English, second version uses German
|
|
|
|
### Called From
|
|
- Line 1514: `self.link_to_gitea(project)` in `gitea_operation` method
|
|
|
|
### Recommendation
|
|
**Safe to consolidate**: Keep the second version (line 2392) as it contains the actual implementation. The first version is just a placeholder.
|
|
|
|
---
|
|
|
|
## 3. `test_gitea_connection` (line 1757 vs line 2457)
|
|
|
|
### First Version (line 1757-1791)
|
|
```python
|
|
def test_gitea_connection(self, project):
|
|
"""Test Gitea connection and show permissions"""
|
|
try:
|
|
# Test API connection
|
|
user_info = self.repo_manager.client.get_user_info()
|
|
|
|
info = "Gitea-Verbindung erfolgreich!\n\n"
|
|
info += f"Benutzer: {user_info.get('username', 'Unknown')}\n"
|
|
info += f"E-Mail: {user_info.get('email', 'Unknown')}\n"
|
|
info += f"Admin: {'Ja' if user_info.get('is_admin', False) else 'Nein'}\n\n"
|
|
|
|
# Check organizations
|
|
orgs = self.repo_manager.client.list_user_organizations()
|
|
if orgs:
|
|
info += "Organisationen:\n"
|
|
for org in orgs:
|
|
org_name = org.get('username', org.get('name', 'Unknown'))
|
|
info += f" • {org_name}"
|
|
|
|
# Check teams/permissions in org
|
|
teams = self.repo_manager.client.get_user_teams_in_org(org_name)
|
|
if teams:
|
|
team_names = [t.get('name', 'Unknown') for t in teams]
|
|
info += f" (Teams: {', '.join(team_names)})"
|
|
info += "\n"
|
|
else:
|
|
info += "Keine Organisationen\n"
|
|
|
|
info += f"\nServer: {self.repo_manager.client.config.base_url}"
|
|
|
|
messagebox.showinfo("Verbindungstest", info)
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("Verbindungsfehler", f"Konnte keine Verbindung zu Gitea herstellen:\n\n{str(e)}")
|
|
```
|
|
|
|
### Second Version (line 2457-2554)
|
|
```python
|
|
def test_gitea_connection(self, project=None):
|
|
"""Test Gitea connection and show detailed information"""
|
|
try:
|
|
# Test basic connection
|
|
user_info = self.repo_manager.client.get_user_info()
|
|
username = user_info.get('username', 'Unknown')
|
|
|
|
# Get organizations
|
|
orgs = self.repo_manager.client.list_user_organizations()
|
|
org_names = [org['username'] for org in orgs]
|
|
|
|
# Build info message
|
|
info = f"✅ Gitea Verbindung erfolgreich!\n\n"
|
|
info += f"Benutzer: {username}\n"
|
|
info += f"Server: {self.repo_manager.client.config.base_url}\n"
|
|
info += f"Organisationen: {', '.join(org_names) if org_names else 'Keine'}\n\n"
|
|
|
|
# Check organization permissions if in org mode
|
|
if hasattr(self, 'gitea_explorer') and self.gitea_explorer.view_mode == "organization":
|
|
org_name = self.gitea_explorer.organization_name
|
|
if org_name:
|
|
teams = self.repo_manager.client.get_user_teams_in_org(org_name)
|
|
if teams:
|
|
info += f"Teams in {org_name}:\n"
|
|
for team in teams:
|
|
info += f" - {team.get('name', 'Unknown')} "
|
|
perms = []
|
|
if team.get('can_create_org_repo'):
|
|
perms.append("kann Repos erstellen")
|
|
if team.get('permission') == 'admin':
|
|
perms.append("Admin")
|
|
elif team.get('permission') == 'write':
|
|
perms.append("Schreiben")
|
|
elif team.get('permission') == 'read':
|
|
perms.append("Lesen")
|
|
info += f"({', '.join(perms) if perms else 'keine Rechte'})\n"
|
|
else:
|
|
info += f"⚠️ Keine Teams in Organisation {org_name} gefunden!\n"
|
|
info += "Dies könnte der Grund sein, warum Repositories nicht erstellt werden können.\n"
|
|
|
|
info += "\n"
|
|
|
|
# If we have a project, show its remote info
|
|
if project:
|
|
project_path = Path(project.path) if hasattr(project, 'path') else Path(project['path'])
|
|
if project_path.exists():
|
|
success, remotes = self.repo_manager.git_ops.remote_list(project_path)
|
|
if success and remotes:
|
|
info += f"Git Remote URLs:\n{remotes}\n\n"
|
|
|
|
# Check if .git exists
|
|
if (project_path / '.git').exists():
|
|
# Get current branch
|
|
success, branch_out = self.repo_manager.git_ops._run_git_command(
|
|
["git", "branch", "--show-current"], cwd=project_path
|
|
)
|
|
if success:
|
|
info += f"Aktueller Branch: {branch_out.strip()}\n"
|
|
|
|
# List repositories in current mode
|
|
info += "Repositories:\n"
|
|
try:
|
|
if hasattr(self, 'gitea_explorer'):
|
|
if self.gitea_explorer.view_mode == "organization" and self.gitea_explorer.organization_name:
|
|
# List org repos
|
|
org_repos = self.repo_manager.list_organization_repositories(self.gitea_explorer.organization_name)
|
|
info += f"In Organisation {self.gitea_explorer.organization_name}: {len(org_repos)} Repositories\n"
|
|
for repo in org_repos[:5]: # Show first 5
|
|
info += f" - {repo['name']}\n"
|
|
if len(org_repos) > 5:
|
|
info += f" ... und {len(org_repos) - 5} weitere\n"
|
|
else:
|
|
# List user repos
|
|
user_repos = self.repo_manager.list_all_repositories()
|
|
info += f"Benutzer Repositories: {len(user_repos)}\n"
|
|
for repo in user_repos[:5]: # Show first 5
|
|
info += f" - {repo['name']} (Owner: {repo.get('owner', {}).get('username', 'Unknown')})\n"
|
|
if len(user_repos) > 5:
|
|
info += f" ... und {len(user_repos) - 5} weitere\n"
|
|
except Exception as e:
|
|
info += f"Fehler beim Abrufen der Repositories: {str(e)}\n"
|
|
|
|
# Show log file location
|
|
log_file = Path.home() / ".claude_project_manager" / "gitea_operations.log"
|
|
info += f"\nLog-Datei: {log_file}"
|
|
|
|
messagebox.showinfo("Gitea Verbindungstest", info)
|
|
|
|
except Exception as e:
|
|
error_msg = f"❌ Gitea Verbindung fehlgeschlagen!\n\n"
|
|
error_msg += f"Fehler: {str(e)}\n\n"
|
|
error_msg += f"Server: {self.repo_manager.client.config.base_url}\n"
|
|
error_msg += "\nBitte prüfen Sie:\n"
|
|
error_msg += "- Netzwerkverbindung\n"
|
|
error_msg += "- API Token Gültigkeit\n"
|
|
error_msg += "- Server Erreichbarkeit"
|
|
|
|
messagebox.showerror("Gitea Verbindungstest", error_msg)
|
|
```
|
|
|
|
### Key Differences
|
|
- **Parameter**: First version requires `project`, second version has `project=None` (optional)
|
|
- **Detail level**: Second version shows much more information including:
|
|
- Team permissions with detailed permission levels
|
|
- Project-specific git remote info if project is provided
|
|
- Current branch information
|
|
- Repository listings (user/org mode aware)
|
|
- Log file location
|
|
- **UI elements**: First version shows email and admin status, second version doesn't
|
|
- **Error handling**: Second version has more detailed error messages with checkmark/cross emojis
|
|
|
|
### Called From
|
|
- Line 1522: `self.test_gitea_connection(project)` in `gitea_operation` method
|
|
|
|
### Recommendation
|
|
**Requires careful consolidation**: The second version is more comprehensive but has an optional parameter. Since the call from `gitea_operation` always passes a project, we should keep the enhanced functionality of the second version but ensure it works correctly when a project is provided.
|
|
|
|
---
|
|
|
|
## 4. `verify_repository_on_gitea` (line 1792 vs line 2556)
|
|
|
|
### First Version (line 1792-1849+)
|
|
```python
|
|
def verify_repository_on_gitea(self, project):
|
|
"""Verify if repository exists on Gitea and show detailed info"""
|
|
import re
|
|
from pathlib import Path
|
|
|
|
project_path = Path(project.path)
|
|
|
|
# Check if it's a git repo
|
|
if not (project_path / ".git").exists():
|
|
messagebox.showinfo("Info", "Dies ist kein Git-Repository.")
|
|
return
|
|
|
|
# Get remote info
|
|
git_ops = self.repo_manager.git_ops
|
|
success, remotes = git_ops.remote_list(project_path)
|
|
|
|
if not success or not remotes:
|
|
messagebox.showinfo("Info", "Kein Remote-Repository konfiguriert.")
|
|
return
|
|
|
|
# Extract owner and repo name from remote URL
|
|
match = re.search(r'gitea-undso\.intelsight\.de[:/]([^/]+)/([^/\.]+)', remotes)
|
|
if not match:
|
|
messagebox.showwarning("Warnung", f"Konnte Repository-Info nicht aus Remote-URL extrahieren:\n{remotes}")
|
|
return
|
|
|
|
remote_owner = match.group(1)
|
|
remote_repo = match.group(2)
|
|
|
|
info = f"Suche Repository: {remote_owner}/{remote_repo}\n\n"
|
|
|
|
# Check if repo exists under detected owner
|
|
try:
|
|
repo = self.repo_manager.client.get_repository(remote_owner, remote_repo)
|
|
info += "✅ Repository gefunden!\n\n"
|
|
info += f"Name: {repo.get('name', 'Unknown')}\n"
|
|
info += f"Owner: {repo.get('owner', {}).get('username', 'Unknown')}\n"
|
|
info += f"URL: {repo.get('html_url', 'Unknown')}\n"
|
|
info += f"Clone URL: {repo.get('clone_url', 'Unknown')}\n"
|
|
info += f"Privat: {'Ja' if repo.get('private', False) else 'Nein'}\n"
|
|
info += f"Größe: {repo.get('size', 0) / 1024:.1f} KB\n"
|
|
info += f"Default Branch: {repo.get('default_branch', 'Keiner')}\n"
|
|
info += f"Erstellt: {repo.get('created_at', 'Unknown')}\n"
|
|
info += f"Zuletzt aktualisiert: {repo.get('updated_at', 'Unknown')}\n"
|
|
|
|
# Check for content
|
|
has_content = repo.get('size', 0) > 0 or repo.get('default_branch')
|
|
if not has_content:
|
|
info += "\n⚠️ WARNUNG: Repository scheint leer zu sein!\n"
|
|
|
|
except Exception as e:
|
|
info += f"❌ Repository nicht unter {remote_owner}/{remote_repo} gefunden!\n"
|
|
info += f"Fehler: {str(e)}\n\n"
|
|
|
|
# Search in all locations
|
|
info += "Suche in allen verfügbaren Orten:\n\n"
|
|
|
|
# Check user repos
|
|
# ... (continues beyond visible range)
|
|
```
|
|
|
|
### Second Version (line 2556-2656)
|
|
```python
|
|
def verify_repository_on_gitea(self, project):
|
|
"""Verify if repository exists on Gitea and show detailed info"""
|
|
project_name = project.name if hasattr(project, 'name') else project.get('name', 'Unknown')
|
|
project_path = Path(project.path) if hasattr(project, 'path') else Path(project['path'])
|
|
|
|
info = f"Repository Verifizierung für: {project_name}\n"
|
|
info += "=" * 50 + "\n\n"
|
|
|
|
# Check git remotes
|
|
git_ops = self.repo_manager.git_ops
|
|
success, remotes = git_ops.remote_list(project_path)
|
|
if success and remotes:
|
|
info += f"Git Remote URLs:\n{remotes}\n\n"
|
|
|
|
# Extract repo name and owner from remote
|
|
import re
|
|
match = re.search(r'gitea-undso\.intelsight\.de[:/]([^/]+)/([^/\.]+)', remotes)
|
|
if match:
|
|
remote_owner = match.group(1)
|
|
remote_repo = match.group(2)
|
|
info += f"Remote zeigt auf: {remote_owner}/{remote_repo}\n\n"
|
|
|
|
# Search for repository
|
|
info += "Suche Repository auf Gitea:\n"
|
|
|
|
# 1. Check exact location
|
|
try:
|
|
repo = self.repo_manager.client.get_repository(remote_owner, remote_repo)
|
|
info += f"✅ Gefunden unter {remote_owner}/{remote_repo}\n"
|
|
info += f" URL: {repo.get('html_url', 'Unknown')}\n"
|
|
info += f" Größe: {repo.get('size', 0)} bytes\n"
|
|
info += f" Erstellt: {repo.get('created_at', 'Unknown')}\n"
|
|
info += f" Aktualisiert: {repo.get('updated_at', 'Unknown')}\n"
|
|
info += f" Default Branch: {repo.get('default_branch', 'Unknown')}\n"
|
|
info += f" Privat: {'Ja' if repo.get('private') else 'Nein'}\n"
|
|
except Exception as e:
|
|
info += f"❌ Nicht gefunden unter {remote_owner}/{remote_repo}\n"
|
|
info += f" Fehler: {str(e)}\n"
|
|
|
|
# 2. Search in all user repos
|
|
info += "\nSuche in allen Benutzer-Repositories:\n"
|
|
try:
|
|
user_repos = self.repo_manager.list_all_repositories()
|
|
matching_repos = [r for r in user_repos if r['name'] == remote_repo or r['name'] == project_name]
|
|
if matching_repos:
|
|
for repo in matching_repos:
|
|
info += f"✅ Gefunden: {repo['owner']['username']}/{repo['name']}\n"
|
|
info += f" URL: {repo.get('html_url', 'Unknown')}\n"
|
|
else:
|
|
info += "❌ Nicht in Benutzer-Repositories gefunden\n"
|
|
except Exception as e:
|
|
info += f"❌ Fehler beim Durchsuchen: {str(e)}\n"
|
|
|
|
# 3. Search in organization
|
|
if hasattr(self, 'gitea_explorer') and self.gitea_explorer.organization_name:
|
|
info += f"\nSuche in Organisation {self.gitea_explorer.organization_name}:\n"
|
|
try:
|
|
org_repos = self.repo_manager.list_organization_repositories(self.gitea_explorer.organization_name)
|
|
matching_repos = [r for r in org_repos if r['name'] == remote_repo or r['name'] == project_name]
|
|
if matching_repos:
|
|
for repo in matching_repos:
|
|
info += f"✅ Gefunden: {repo['name']}\n"
|
|
info += f" URL: {repo.get('html_url', 'Unknown')}\n"
|
|
else:
|
|
info += "❌ Nicht in Organisation gefunden\n"
|
|
except Exception as e:
|
|
info += f"❌ Fehler beim Durchsuchen: {str(e)}\n"
|
|
else:
|
|
info += "❌ Keine Git Remote gefunden!\n"
|
|
|
|
# Check for debug file
|
|
debug_file = project_path / "gitea_push_debug.txt"
|
|
if debug_file.exists():
|
|
info += f"\n\n📄 Debug-Datei gefunden: {debug_file}\n"
|
|
try:
|
|
with open(debug_file, 'r', encoding='utf-8') as f:
|
|
debug_content = f.read()
|
|
info += "Debug-Inhalt:\n" + "-" * 30 + "\n"
|
|
info += debug_content[:1000] # First 1000 chars
|
|
if len(debug_content) > 1000:
|
|
info += "\n... (gekürzt)"
|
|
except:
|
|
info += "Konnte Debug-Datei nicht lesen\n"
|
|
|
|
# Show in scrollable dialog
|
|
dialog = ctk.CTkToplevel(self.root)
|
|
dialog.title("Repository Verifizierung")
|
|
dialog.geometry("800x600")
|
|
|
|
text_widget = ctk.CTkTextbox(dialog, wrap="word")
|
|
text_widget.pack(fill="both", expand=True, padx=10, pady=10)
|
|
text_widget.insert("1.0", info)
|
|
text_widget.configure(state="disabled")
|
|
|
|
close_btn = ctk.CTkButton(
|
|
dialog,
|
|
text="Schließen",
|
|
command=dialog.destroy
|
|
)
|
|
close_btn.pack(pady=10)
|
|
```
|
|
|
|
### Key Differences
|
|
- **Project access**: Second version handles both object attributes and dictionary access for project data
|
|
- **Pre-checks**: First version checks if it's a git repo and has remotes before proceeding
|
|
- **Display method**: First version shows info in messagebox, second version uses a scrollable CTkToplevel dialog
|
|
- **Additional features**: Second version includes:
|
|
- Debug file checking and display
|
|
- More structured repository search (exact location, user repos, org repos)
|
|
- Header with project name
|
|
- **Size display**: First version shows size in KB, second version in bytes
|
|
|
|
### Called From
|
|
- Line 1524: `self.verify_repository_on_gitea(project)` in `gitea_operation` method
|
|
- Line 1987: `self.verify_repository_on_gitea(project)` in `fix_repository_issues` method (nested function `check_on_gitea`)
|
|
|
|
### Recommendation
|
|
**Requires careful consolidation**: Both versions have valuable features. The second version's scrollable dialog is better for displaying detailed information, but the first version's pre-checks are important. Combine both:
|
|
- Keep pre-checks from first version
|
|
- Keep the enhanced display and debug file checking from second version
|
|
- Ensure consistent project data access
|
|
|
|
---
|
|
|
|
## Summary and Recommendations
|
|
|
|
1. **`manage_branches`**: Delete first version (placeholder), keep second version
|
|
2. **`link_to_gitea`**: Delete first version (placeholder), keep second version
|
|
3. **`test_gitea_connection`**: Merge both versions, keeping the enhanced functionality of the second while ensuring it works with required project parameter
|
|
4. **`verify_repository_on_gitea`**: Merge both versions, combining pre-checks from first with enhanced display from second
|
|
|
|
All duplicates are called from the same `gitea_operation` method, so consolidation is safe. The first versions of `manage_branches` and `link_to_gitea` are just placeholders that were later implemented fully in the second versions. |