From 924317d4765ecf9bf1f8fa9ccd579d64367574b6 Mon Sep 17 00:00:00 2001 From: medenor <60567543+Medenor@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:17:51 +0100 Subject: [PATCH 1/4] Add Codeberg repository support --- api/data_pipeline.py | 80 ++++++++++++++++- api/simple_chat.py | 2 +- api/websocket_wiki.py | 2 +- src/app/[owner]/[repo]/page.tsx | 117 +++++++++++++++++++++++-- src/app/page.tsx | 16 +++- src/components/ConfigurationModal.tsx | 4 +- src/components/ModelSelectionModal.tsx | 4 +- src/components/TokenInput.tsx | 14 ++- src/messages/en.json | 6 +- src/messages/es.json | 6 +- src/messages/fr.json | 6 +- src/messages/ja.json | 6 +- src/messages/kr.json | 6 +- src/messages/pt-br.json | 6 +- src/messages/ru.json | 6 +- src/messages/vi.json | 6 +- src/messages/zh-tw.json | 6 +- src/messages/zh.json | 6 +- test/test_extract_repo_name.py | 8 ++ 19 files changed, 257 insertions(+), 50 deletions(-) diff --git a/api/data_pipeline.py b/api/data_pipeline.py index 67b2dcbf..0fe796d4 100644 --- a/api/data_pipeline.py +++ b/api/data_pipeline.py @@ -68,7 +68,7 @@ def count_tokens(text: str, embedder_type: str = None, is_ollama_embedder: bool def download_repo(repo_url: str, local_path: str, repo_type: str = None, access_token: str = None) -> str: """ - Downloads a Git repository (GitHub, GitLab, or Bitbucket) to a specified local path. + Downloads a Git repository (GitHub, GitLab, Bitbucket, or Codeberg) to a specified local path. Args: repo_type(str): Type of repository @@ -103,7 +103,7 @@ def download_repo(repo_url: str, local_path: str, repo_type: str = None, access_ if access_token: parsed = urlparse(repo_url) # Determine the repository type and format the URL accordingly - if repo_type == "github": + if repo_type == "github" or repo_type == "codeberg": # Format: https://{token}@{domain}/owner/repo.git # Works for both github.com and enterprise GitHub domains clone_url = urlunparse((parsed.scheme, f"{access_token}@{parsed.netloc}", parsed.path, '', '', '')) @@ -675,6 +675,77 @@ def get_bitbucket_file_content(repo_url: str, file_path: str, access_token: str raise ValueError(f"Failed to get file content: {str(e)}") +def get_codeberg_file_content(repo_url: str, file_path: str, access_token: str = None) -> str: + """Retrieve file content from a Codeberg (Forgejo/Gitea) repository.""" + try: + parsed_url = urlparse(repo_url) + if not parsed_url.scheme or not parsed_url.netloc: + raise ValueError("Not a valid Codeberg repository URL") + + repo_path = parsed_url.path.strip('/').replace('.git', '') + if not repo_path: + raise ValueError("Invalid Codeberg URL format") + + # Encode each part of the repo path separately to preserve hierarchy + encoded_repo_path = '/'.join(quote(part, safe='') for part in repo_path.split('/')) + encoded_file_path = quote(file_path, safe='') + + api_base = f"{parsed_url.scheme}://{parsed_url.netloc}" + if parsed_url.port not in (None, 80, 443): + api_base += f":{parsed_url.port}" + api_base += "/api/v1" + + headers = { + 'Accept': 'application/json' + } + if access_token: + headers['Authorization'] = f"token {access_token}" + + # Fetch repository info to determine default branch + default_branch = 'main' + try: + repo_info_url = f"{api_base}/repos/{encoded_repo_path}" + repo_info_response = requests.get(repo_info_url, headers=headers) + if repo_info_response.status_code == 200: + repo_info = repo_info_response.json() + default_branch = repo_info.get('default_branch') or default_branch + else: + logger.warning("Could not fetch Codeberg repository info, using 'main' as default branch") + except Exception as exc: + logger.warning(f"Error fetching Codeberg repository info: {exc}. Using 'main' as default branch") + + file_url = f"{api_base}/repos/{encoded_repo_path}/contents/{encoded_file_path}?ref={quote(default_branch, safe='')}" + logger.info(f"Fetching file content from Codeberg API: {file_url}") + try: + response = requests.get(file_url, headers=headers) + if response.status_code == 200: + data = response.json() + content = data.get('content', '') + encoding = data.get('encoding', 'base64') + if encoding == 'base64': + try: + return base64.b64decode(content.encode('utf-8')).decode('utf-8') + except Exception as decode_error: + raise ValueError(f"Failed to decode Codeberg file content: {decode_error}") + return content + elif response.status_code == 404: + raise ValueError("File not found on Codeberg. Please check the file path and repository.") + elif response.status_code == 401: + raise ValueError("Unauthorized access to Codeberg. Please check your access token.") + elif response.status_code == 403: + raise ValueError("Forbidden access to Codeberg. You might not have permission to access this file.") + elif response.status_code >= 500: + raise ValueError("Codeberg server error. Please try again later.") + else: + response.raise_for_status() + return response.text + except RequestException as exc: + raise ValueError(f"Error fetching file content: {exc}") + + except Exception as e: + raise ValueError(f"Failed to get file content: {str(e)}") + + def get_file_content(repo_url: str, file_path: str, repo_type: str = None, access_token: str = None) -> str: """ Retrieves the content of a file from a Git repository (GitHub or GitLab). @@ -697,6 +768,8 @@ def get_file_content(repo_url: str, file_path: str, repo_type: str = None, acces return get_gitlab_file_content(repo_url, file_path, access_token) elif repo_type == "bitbucket": return get_bitbucket_file_content(repo_url, file_path, access_token) + elif repo_type == "codeberg": + return get_codeberg_file_content(repo_url, file_path, access_token) else: raise ValueError("Unsupported repository type. Only GitHub, GitLab, and Bitbucket are supported.") @@ -754,10 +827,11 @@ def _extract_repo_name_from_url(self, repo_url_or_path: str, repo_type: str) -> # Extract owner and repo name to create unique identifier url_parts = repo_url_or_path.rstrip('/').split('/') - if repo_type in ["github", "gitlab", "bitbucket"] and len(url_parts) >= 5: + if repo_type in ["github", "gitlab", "bitbucket", "codeberg"] and len(url_parts) >= 5: # GitHub URL format: https://github.com/owner/repo # GitLab URL format: https://gitlab.com/owner/repo or https://gitlab.com/group/subgroup/repo # Bitbucket URL format: https://bitbucket.org/owner/repo + # Codeberg URL format: https://codeberg.org/owner/repo owner = url_parts[-2] repo = url_parts[-1].replace(".git", "") repo_name = f"{owner}_{repo}" diff --git a/api/simple_chat.py b/api/simple_chat.py index 06d329a2..767c778e 100644 --- a/api/simple_chat.py +++ b/api/simple_chat.py @@ -60,7 +60,7 @@ class ChatCompletionRequest(BaseModel): messages: List[ChatMessage] = Field(..., description="List of chat messages") filePath: Optional[str] = Field(None, description="Optional path to a file in the repository to include in the prompt") token: Optional[str] = Field(None, description="Personal access token for private repositories") - type: Optional[str] = Field("github", description="Type of repository (e.g., 'github', 'gitlab', 'bitbucket')") + type: Optional[str] = Field("github", description="Type of repository (e.g., 'github', 'gitlab', 'bitbucket', 'codeberg')") # model parameters provider: str = Field("google", description="Model provider (google, openai, openrouter, ollama, bedrock, azure)") diff --git a/api/websocket_wiki.py b/api/websocket_wiki.py index 2a7cce9e..cdec57a3 100644 --- a/api/websocket_wiki.py +++ b/api/websocket_wiki.py @@ -37,7 +37,7 @@ class ChatCompletionRequest(BaseModel): messages: List[ChatMessage] = Field(..., description="List of chat messages") filePath: Optional[str] = Field(None, description="Optional path to a file in the repository to include in the prompt") token: Optional[str] = Field(None, description="Personal access token for private repositories") - type: Optional[str] = Field("github", description="Type of repository (e.g., 'github', 'gitlab', 'bitbucket')") + type: Optional[str] = Field("github", description="Type of repository (e.g., 'github', 'gitlab', 'bitbucket', 'codeberg')") # model parameters provider: str = Field("google", description="Model provider (google, openai, openrouter, ollama, azure)") diff --git a/src/app/[owner]/[repo]/page.tsx b/src/app/[owner]/[repo]/page.tsx index 98ffc6fb..df62f3f1 100644 --- a/src/app/[owner]/[repo]/page.tsx +++ b/src/app/[owner]/[repo]/page.tsx @@ -14,6 +14,7 @@ import Link from 'next/link'; import { useParams, useSearchParams } from 'next/navigation'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FaBitbucket, FaBookOpen, FaComments, FaDownload, FaExclamationTriangle, FaFileExport, FaFolder, FaGithub, FaGitlab, FaHome, FaSync, FaTimes } from 'react-icons/fa'; +import { SiCodeberg } from 'react-icons/si'; // Define the WikiSection and WikiStructure types directly in this file // since the imported types don't have the sections and rootSections properties interface WikiSection { @@ -173,6 +174,18 @@ const createBitbucketHeaders = (bitbucketToken: string): HeadersInit => { return headers; }; +const createCodebergHeaders = (codebergToken: string): HeadersInit => { + const headers: HeadersInit = { + 'Accept': 'application/json', + }; + + if (codebergToken) { + headers['Authorization'] = `token ${codebergToken}`; + } + + return headers; +}; + export default function RepoWikiPage() { // Get route parameters and search params @@ -203,11 +216,13 @@ export default function RepoWikiPage() { })(); const repoType = repoHost?.includes('bitbucket') ? 'bitbucket' - : repoHost?.includes('gitlab') - ? 'gitlab' - : repoHost?.includes('github') - ? 'github' - : searchParams.get('type') || 'github'; + : repoHost?.includes('codeberg') + ? 'codeberg' + : repoHost?.includes('gitlab') + ? 'gitlab' + : repoHost?.includes('github') + ? 'github' + : searchParams.get('type') || 'github'; // Import language context for translations const { messages } = useLanguage(); @@ -1314,8 +1329,8 @@ IMPORTANT: } } catch (err) { console.warn('Could not fetch README.md, continuing with empty README', err); - } } + } else if (effectiveRepoInfo.type === 'gitlab') { // GitLab API approach const projectPath = extractUrlPath(effectiveRepoInfo.repoUrl ?? '')?.replace(/\.git$/, '') || `${owner}/${repo}`; @@ -1399,6 +1414,92 @@ IMPORTANT: throw err; } } + else if (effectiveRepoInfo.type === 'codeberg') { + const repoPathRaw = extractUrlPath(effectiveRepoInfo.repoUrl ?? '')?.replace(/\.git$/, '') || `${owner}/${repo}`; + const encodedRepoPath = repoPathRaw.split('/').map(encodeURIComponent).join('/'); + + let apiBaseUrl = 'https://codeberg.org/api/v1'; + if (effectiveRepoInfo.repoUrl) { + try { + const parsedUrl = new URL(effectiveRepoInfo.repoUrl); + apiBaseUrl = `${parsedUrl.protocol}//${parsedUrl.hostname}${parsedUrl.port ? `:${parsedUrl.port}` : ''}/api/v1`; + } catch (err) { + console.warn('Could not parse Codeberg repository URL, defaulting to public API', err); + } + } + + const headers = createCodebergHeaders(currentToken); + let defaultBranchLocal = 'main'; + let apiErrorDetails = ''; + + // Fetch repository info to determine default branch + try { + const repoInfoResponse = await fetch(`${apiBaseUrl}/repos/${encodedRepoPath}`, { headers }); + if (repoInfoResponse.ok) { + const repoInfoData = await repoInfoResponse.json(); + defaultBranchLocal = repoInfoData?.default_branch || defaultBranchLocal; + } else { + const errorText = await repoInfoResponse.text(); + apiErrorDetails = `Status: ${repoInfoResponse.status}, Response: ${errorText}`; + console.warn(`Could not fetch Codeberg repository info: ${apiErrorDetails}`); + } + } catch (err) { + console.warn('Network error fetching Codeberg repository info:', err); + } + + setDefaultBranch(defaultBranchLocal); + + let treeData: unknown = null; + try { + const treeResponse = await fetch(`${apiBaseUrl}/repos/${encodedRepoPath}/git/trees/${encodeURIComponent(defaultBranchLocal)}?recursive=1`, { headers }); + if (treeResponse.ok) { + treeData = await treeResponse.json(); + } else { + const errorText = await treeResponse.text(); + apiErrorDetails = `Status: ${treeResponse.status}, Response: ${errorText}`; + console.error(`Error fetching Codeberg repository structure: ${apiErrorDetails}`); + } + } catch (err) { + console.error('Network error fetching Codeberg repository tree:', err); + } + + const treeEntries = (treeData && typeof treeData === 'object' && 'tree' in (treeData as Record) + ? (treeData as { tree: Array<{ type: string; path: string }> }).tree + : (treeData as { entries?: Array<{ type: string; path: string }> })?.entries) || []; + + if (!Array.isArray(treeEntries) || treeEntries.length === 0) { + if (apiErrorDetails) { + throw new Error(`Could not fetch repository structure. Codeberg API Error: ${apiErrorDetails}`); + } else { + throw new Error('Could not fetch repository structure. Repository might not exist, be empty or private.'); + } + } + + fileTreeData = treeEntries + .filter((item: { type: string; path: string }) => item.type === 'blob') + .map((item: { type: string; path: string }) => item.path) + .join('\n'); + + // Fetch README if available + try { + const readmeResponse = await fetch(`${apiBaseUrl}/repos/${encodedRepoPath}/readme?ref=${encodeURIComponent(defaultBranchLocal)}`, { headers }); + if (readmeResponse.ok) { + const readmeData = await readmeResponse.json(); + if (readmeData?.content) { + const sanitized = (readmeData.content as string).replace(/\s/g, ''); + try { + readmeContent = atob(sanitized); + } catch (decodeError) { + console.warn('Failed to decode Codeberg README content:', decodeError); + } + } + } else { + console.warn(`Could not fetch Codeberg README.md, status: ${readmeResponse.status}`); + } + } catch (err) { + console.warn('Could not fetch Codeberg README.md, continuing with empty README', err); + } + } else if (effectiveRepoInfo.type === 'bitbucket') { // Bitbucket API approach const repoPath = extractUrlPath(effectiveRepoInfo.repoUrl ?? '') ?? `${owner}/${repo}`; @@ -2059,6 +2160,8 @@ IMPORTANT: ) : effectiveRepoInfo.type === 'gitlab' ? ( + ) : effectiveRepoInfo.type === 'codeberg' ? ( + ) : ( )} @@ -2269,7 +2372,7 @@ IMPORTANT: onApply={confirmRefresh} showWikiType={true} showTokenInput={effectiveRepoInfo.type !== 'local' && !currentToken} // Show token input if not local and no current token - repositoryType={effectiveRepoInfo.type as 'github' | 'gitlab' | 'bitbucket'} + repositoryType={effectiveRepoInfo.type as 'github' | 'gitlab' | 'bitbucket' | 'codeberg'} authRequired={authRequired} authCode={authCode} setAuthCode={setAuthCode} diff --git a/src/app/page.tsx b/src/app/page.tsx index 9e05a2ef..cb75a21a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -134,7 +134,7 @@ export default function Home() { const [excludedFiles, setExcludedFiles] = useState(''); const [includedDirs, setIncludedDirs] = useState(''); const [includedFiles, setIncludedFiles] = useState(''); - const [selectedPlatform, setSelectedPlatform] = useState<'github' | 'gitlab' | 'bitbucket'>('github'); + const [selectedPlatform, setSelectedPlatform] = useState<'github' | 'gitlab' | 'bitbucket' | 'codeberg'>('github'); const [accessToken, setAccessToken] = useState(''); const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); @@ -212,6 +212,8 @@ export default function Home() { type = 'gitlab'; } else if (domain?.includes('bitbucket.org') || domain?.includes('bitbucket.')) { type = 'bitbucket'; + } else if (domain?.includes('codeberg.org') || domain?.includes('codeberg.')) { + type = 'codeberg'; } else { type = 'web'; // fallback for other git hosting services } @@ -259,6 +261,10 @@ export default function Home() { return; } + if (parsedRepo.type && !['local', 'web'].includes(parsedRepo.type)) { + setSelectedPlatform(parsedRepo.type as 'github' | 'gitlab' | 'bitbucket' | 'codeberg'); + } + // If valid, open the configuration modal setError(null); setIsConfigModalOpen(true); @@ -341,6 +347,12 @@ export default function Home() { return; } + const detectedType = parsedRepo.type; + const effectiveRepoType = detectedType === 'web' ? selectedPlatform : detectedType; + if (effectiveRepoType && effectiveRepoType !== selectedPlatform && effectiveRepoType !== 'local') { + setSelectedPlatform(effectiveRepoType as 'github' | 'gitlab' | 'bitbucket' | 'codeberg'); + } + const { owner, repo, type, localPath } = parsedRepo; // Store tokens in query params if they exist @@ -349,7 +361,7 @@ export default function Home() { params.append('token', accessToken); } // Always include the type parameter - params.append('type', (type == 'local' ? type : selectedPlatform) || 'github'); + params.append('type', (type == 'local' ? type : effectiveRepoType) || 'github'); // Add local path if it exists if (localPath) { params.append('local_path', encodeURIComponent(localPath)); diff --git a/src/components/ConfigurationModal.tsx b/src/components/ConfigurationModal.tsx index 7a1dae69..7400874b 100644 --- a/src/components/ConfigurationModal.tsx +++ b/src/components/ConfigurationModal.tsx @@ -32,8 +32,8 @@ interface ConfigurationModalProps { setCustomModel: (value: string) => void; // Platform selection - selectedPlatform: 'github' | 'gitlab' | 'bitbucket'; - setSelectedPlatform: (value: 'github' | 'gitlab' | 'bitbucket') => void; + selectedPlatform: 'github' | 'gitlab' | 'bitbucket' | 'codeberg'; + setSelectedPlatform: (value: 'github' | 'gitlab' | 'bitbucket' | 'codeberg') => void; // Access token accessToken: string; diff --git a/src/components/ModelSelectionModal.tsx b/src/components/ModelSelectionModal.tsx index 5a8ed3fe..cf856542 100644 --- a/src/components/ModelSelectionModal.tsx +++ b/src/components/ModelSelectionModal.tsx @@ -37,7 +37,7 @@ interface ModelSelectionModalProps { // Token input for refresh showTokenInput?: boolean; - repositoryType?: 'github' | 'gitlab' | 'bitbucket'; + repositoryType?: 'github' | 'gitlab' | 'bitbucket' | 'codeberg'; // Authentication authRequired?: boolean; authCode?: string; @@ -91,7 +91,7 @@ export default function ModelSelectionModal({ // Token input state const [localAccessToken, setLocalAccessToken] = useState(''); - const [localSelectedPlatform, setLocalSelectedPlatform] = useState<'github' | 'gitlab' | 'bitbucket'>(repositoryType); + const [localSelectedPlatform, setLocalSelectedPlatform] = useState<'github' | 'gitlab' | 'bitbucket' | 'codeberg'>(repositoryType); const [showTokenSection, setShowTokenSection] = useState(showTokenInput); // Reset local state when modal is opened diff --git a/src/components/TokenInput.tsx b/src/components/TokenInput.tsx index 14fadcd8..07ea9666 100644 --- a/src/components/TokenInput.tsx +++ b/src/components/TokenInput.tsx @@ -4,8 +4,8 @@ import React from 'react'; import { useLanguage } from '@/contexts/LanguageContext'; interface TokenInputProps { - selectedPlatform: 'github' | 'gitlab' | 'bitbucket'; - setSelectedPlatform: (value: 'github' | 'gitlab' | 'bitbucket') => void; + selectedPlatform: 'github' | 'gitlab' | 'bitbucket' | 'codeberg'; + setSelectedPlatform: (value: 'github' | 'gitlab' | 'bitbucket' | 'codeberg') => void; accessToken: string; setAccessToken: (value: string) => void; showTokenSection?: boolean; @@ -76,6 +76,16 @@ export default function TokenInput({ > Bitbucket + )} diff --git a/src/messages/en.json b/src/messages/en.json index a62ee892..25da2a63 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -20,7 +20,7 @@ "home": { "welcome": "Welcome to DeepWiki-Open", "welcomeTagline": "AI-powered documentation for your code repositories", - "description": "Generate comprehensive documentation from GitHub, GitLab, or Bitbucket repositories with just a few clicks.", + "description": "Generate comprehensive documentation from GitHub, GitLab, Bitbucket, or Codeberg repositories with just a few clicks.", "quickStart": "Quick Start", "enterRepoUrl": "Enter a repository URL in one of these formats:", "advancedVisualization": "Advanced Visualization with Mermaid Diagrams", @@ -31,7 +31,7 @@ "form": { "repository": "Repository", "configureWiki": "Configure Wiki", - "repoPlaceholder": "owner/repo or GitHub/GitLab/Bitbucket URL", + "repoPlaceholder": "owner/repo or GitHub/GitLab/Bitbucket/Codeberg URL", "wikiLanguage": "Wiki Language", "modelOptions": "Model Options", "modelProvider": "Model Provider", @@ -111,7 +111,7 @@ "cancel": "Cancel", "home": "Home", "errorTitle": "Error", - "errorMessageDefault": "Please check that your repository exists and is public. Valid formats are \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", or local folder paths like \"C:\\\\path\\\\to\\\\folder\" or \"/path/to/folder\".", + "errorMessageDefault": "Please check that your repository exists and is public. Valid formats are \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", \"https://codeberg.org/owner/repo\", or local folder paths like \"C:\\\\path\\\\to\\\\folder\" or \"/path/to/folder\".", "embeddingErrorDefault": "This error is related to the document embedding system used for analyzing your repository. Please verify your embedding model configuration, API keys, and try again. If the issue persists, consider switching to a different embedding provider in the model settings.", "backToHome": "Back to Home", "exportWiki": "Export Wiki", diff --git a/src/messages/es.json b/src/messages/es.json index cace0b54..50a8ed3e 100644 --- a/src/messages/es.json +++ b/src/messages/es.json @@ -20,7 +20,7 @@ "home": { "welcome": "Bienvenido a DeepWiki", "welcomeTagline": "Documentación impulsada por IA para repositorios de código", - "description": "Genera documentación completa de repositorios GitHub, GitLab o Bitbucket con solo unos clics.", + "description": "Genera documentación completa de repositorios GitHub, GitLab, Bitbucket o Codeberg con solo unos clics.", "quickStart": "Inicio Rápido", "enterRepoUrl": "Ingresa una URL de repositorio en uno de estos formatos:", "advancedVisualization": "Visualización Avanzada con Diagramas Mermaid", @@ -31,7 +31,7 @@ "form": { "repository": "Repositorio", "configureWiki": "Configurar Wiki", - "repoPlaceholder": "propietario/repositorio o URL de GitHub/GitLab/Bitbucket", + "repoPlaceholder": "propietario/repositorio o URL de GitHub/GitLab/Bitbucket/Codeberg", "wikiLanguage": "Idioma del Wiki", "modelOptions": "Opciones de Modelo", "modelProvider": "Proveedor de Modelo", @@ -102,7 +102,7 @@ "cancel": "Cancelar", "home": "Inicio", "errorTitle": "Error", - "errorMessageDefault": "Por favor, compruebe que su repositorio existe y es público. Los formatos válidos son \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", o rutas de carpetas locales como \"C:\\\\path\\\\to\\\\folder\" o \"/path/to/folder\".", + "errorMessageDefault": "Por favor, compruebe que su repositorio existe y es público. Los formatos válidos son \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", \"https://codeberg.org/owner/repo\", o rutas de carpetas locales como \"C:\\\\path\\\\to\\\\folder\" o \"/path/to/folder\".", "embeddingErrorDefault": "Este error está relacionado con el sistema de embebido utilizado para analizar su repositorio. Por favor, verifique la configuración del modelo de embebido, las claves de API y vuelva a intentarlo. Si el problema persiste, considere cambiar al proveedor de embebido diferente en la configuración del modelo.", "backToHome": "Volver al Inicio", "exportWiki": "Exportar Wiki", diff --git a/src/messages/fr.json b/src/messages/fr.json index 4194b31b..b019157f 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -20,7 +20,7 @@ "home": { "welcome": "Bienvenue sur DeepWiki-Open", "welcomeTagline": "Documentation propulsée par l’IA pour vos dépôts de code", - "description": "Générez une documentation complète à partir de dépôts GitHub, GitLab ou Bitbucket en quelques clics.", + "description": "Générez une documentation complète à partir de dépôts GitHub, GitLab, Bitbucket ou Codeberg en quelques clics.", "quickStart": "Démarrage rapide", "enterRepoUrl": "Entrez une URL de dépôt dans l’un des formats suivants :", "advancedVisualization": "Visualisation avancée avec des diagrammes Mermaid", @@ -31,7 +31,7 @@ "form": { "repository": "Dépôt", "configureWiki": "Configurer le Wiki", - "repoPlaceholder": "propriétaire/dépôt ou URL GitHub/GitLab/Bitbucket", + "repoPlaceholder": "propriétaire/dépôt ou URL GitHub/GitLab/Bitbucket/Codeberg", "wikiLanguage": "Langue du Wiki", "modelOptions": "Options du Modèle", "modelProvider": "Fournisseur du Modèle", @@ -111,7 +111,7 @@ "cancel": "Annuler", "home": "Accueil", "errorTitle": "Erreur", - "errorMessageDefault": "Veuillez vérifier que votre dépôt existe et est public. Les formats valides sont \"propriétaire/dépôt\", \"https://github.com/propriétaire/dépôt\", \"https://gitlab.com/propriétaire/dépôt\", \"https://bitbucket.org/propriétaire/dépôt\" ou des chemins locaux comme \"C:\\\\chemin\\\\vers\\\\dossier\" ou \"/chemin/vers/dossier\".", + "errorMessageDefault": "Veuillez vérifier que votre dépôt existe et est public. Les formats valides sont \"propriétaire/dépôt\", \"https://github.com/propriétaire/dépôt\", \"https://gitlab.com/propriétaire/dépôt\", \"https://bitbucket.org/propriétaire/dépôt\", \"https://codeberg.org/propriétaire/dépôt\" ou des chemins locaux comme \"C:\\\\chemin\\\\vers\\\\dossier\" ou \"/chemin/vers/dossier\".", "embeddingErrorDefault": "Cette erreur est liée au système d’indexation utilisé pour analyser votre dépôt. Veuillez vérifier la configuration du modèle d’indexation, les clés API, puis réessayez. Si le problème persiste, envisagez d’utiliser un autre fournisseur d’indexation dans les paramètres du modèle.", "backToHome": "Retour à l’accueil", "exportWiki": "Exporter le Wiki", diff --git a/src/messages/ja.json b/src/messages/ja.json index 8692635f..7e9da250 100644 --- a/src/messages/ja.json +++ b/src/messages/ja.json @@ -20,7 +20,7 @@ "home": { "welcome": "DeepWikiへようこそ", "welcomeTagline": "コードリポジトリのためのAI駆動ドキュメンテーション", - "description": "GitHub、GitLab、またはBitbucketリポジトリから包括的なドキュメントを数クリックで生成します。", + "description": "GitHub、GitLab、Bitbucket、またはCodebergリポジトリから包括的なドキュメントを数クリックで生成します。", "quickStart": "クイックスタート", "enterRepoUrl": "以下のいずれかの形式でリポジトリURLを入力してください:", "advancedVisualization": "Mermaidダイアグラムによる高度な可視化", @@ -31,7 +31,7 @@ "form": { "repository": "リポジトリ", "configureWiki": "Wiki設定", - "repoPlaceholder": "所有者/リポジトリまたはGitHub/GitLab/BitbucketのURL", + "repoPlaceholder": "所有者/リポジトリまたはGitHub/GitLab/Bitbucket/CodebergのURL", "wikiLanguage": "Wiki言語", "modelOptions": "モデルオプション", "modelProvider": "モデルプロバイダー", @@ -102,7 +102,7 @@ "cancel": "キャンセル", "home": "ホーム", "errorTitle": "エラー", - "errorMessageDefault": "リポジトリが存在し、公開されていることを確認してください。有効な形式は「owner/repo」、「https://github.com/owner/repo」、「https://gitlab.com/owner/repo」、「https://bitbucket.org/owner/repo」、またはローカルフォルダパス(例: 「C:\\\\path\\\\to\\\\folder」、「/path/to/folder」)です。", + "errorMessageDefault": "リポジトリが存在し、公開されていることを確認してください。有効な形式は「owner/repo」、「https://github.com/owner/repo」、「https://gitlab.com/owner/repo」、「https://bitbucket.org/owner/repo」、「https://codeberg.org/owner/repo」、またはローカルフォルダパス(例: 「C:\\\\path\\\\to\\\\folder」、「/path/to/folder」)です。", "embeddingErrorDefault": "このエラーは、リポジトリを分析するために使用されるドキュメント埋め込みシステムに関連しています。モデル設定で異なる埋め込みプロバイダーを試してみてください。", "backToHome": "ホームに戻る", "exportWiki": "Wikiをエクスポート", diff --git a/src/messages/kr.json b/src/messages/kr.json index 68666f3d..a2283b61 100644 --- a/src/messages/kr.json +++ b/src/messages/kr.json @@ -20,7 +20,7 @@ "home": { "welcome": "DeepWiki-Open에 오신 것을 환영합니다", "welcomeTagline": "코드 저장소를 위한 AI 기반 문서화", - "description": "GitHub, GitLab 또는 Bitbucket 저장소에서 클릭 한 번으로 종합 문서를 생성하세요.", + "description": "GitHub, GitLab, Bitbucket 또는 Codeberg 저장소에서 클릭 한 번으로 종합 문서를 생성하세요.", "quickStart": "빠른 시작", "enterRepoUrl": "다음 형식 중 하나로 저장소 URL을 입력하세요:", "advancedVisualization": "Mermaid 다이어그램을 활용한 고급 시각화", @@ -31,7 +31,7 @@ "form": { "repository": "저장소", "configureWiki": "위키 구성", - "repoPlaceholder": "owner/repo 또는 GitHub/GitLab/Bitbucket URL", + "repoPlaceholder": "owner/repo 또는 GitHub/GitLab/Bitbucket/Codeberg URL", "wikiLanguage": "위키 언어", "modelOptions": "모델 옵션", "modelProvider": "모델 제공자", @@ -102,7 +102,7 @@ "cancel": "취소", "home": "홈", "errorTitle": "오류", - "errorMessageDefault": "저장소가 존재하며 공개 상태인지 확인해 주세요. 유효한 형식은 \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\" 또는 로컬 폴더 경로 \"C:\\\\path\\\\to\\\\folder\" 혹은 \"/path/to/folder\" 입니다.", + "errorMessageDefault": "저장소가 존재하며 공개 상태인지 확인해 주세요. 유효한 형식은 \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", \"https://codeberg.org/owner/repo\" 또는 로컬 폴더 경로 \"C:\\\\path\\\\to\\\\folder\" 혹은 \"/path/to/folder\" 입니다.", "embeddingErrorDefault": "이 오류는 저장소를 분석하는 데 사용되는 문서 임베딩 시스템과 관련이 있습니다. 임베딩 모델 설정에서 다른 임베딩 제공자를 시도해 보세요. 문제가 지속되면 모델 설정에서 다른 임베딩 제공자를 변경해 보세요.", "backToHome": "홈으로 돌아가기", "exportWiki": "위키 내보내기", diff --git a/src/messages/pt-br.json b/src/messages/pt-br.json index 3bb05575..9112dc7a 100644 --- a/src/messages/pt-br.json +++ b/src/messages/pt-br.json @@ -20,7 +20,7 @@ "home": { "welcome": "Bem-vindo ao DeepWiki-Open", "welcomeTagline": "Documentação com IA para seus repositórios de código", - "description": "Gere documentação completa a partir de repositórios GitHub, GitLab ou Bitbucket com apenas alguns cliques.", + "description": "Gere documentação completa a partir de repositórios GitHub, GitLab, Bitbucket ou Codeberg com apenas alguns cliques.", "quickStart": "Início Rápido", "enterRepoUrl": "Digite uma URL de repositório em um destes formatos:", "advancedVisualization": "Visualização Avançada com Diagramas Mermaid", @@ -31,7 +31,7 @@ "form": { "repository": "Repositório", "configureWiki": "Configurar Wiki", - "repoPlaceholder": "proprietário/repo ou URL do GitHub/GitLab/Bitbucket", + "repoPlaceholder": "proprietário/repo ou URL do GitHub/GitLab/Bitbucket/Codeberg", "wikiLanguage": "Idioma da Wiki", "modelOptions": "Opções de Modelo", "modelProvider": "Provedor de Modelo", @@ -111,7 +111,7 @@ "cancel": "Cancelar", "home": "Início", "errorTitle": "Erro", - "errorMessageDefault": "Verifique se o seu repositório existe e é público. Formatos válidos são \"proprietário/repo\", \"https://github.com/proprietário/repo\", \"https://gitlab.com/proprietário/repo\", \"https://bitbucket.org/proprietário/repo\", ou caminhos de pastas locais como \"C:\\\\caminho\\\\para\\\\pasta\" ou \"/caminho/para/pasta\".", + "errorMessageDefault": "Verifique se o seu repositório existe e é público. Formatos válidos são \"proprietário/repo\", \"https://github.com/proprietário/repo\", \"https://gitlab.com/proprietário/repo\", \"https://bitbucket.org/proprietário/repo\", \"https://codeberg.org/proprietário/repo\", ou caminhos de pastas locais como \"C:\\\\caminho\\\\para\\\\pasta\" ou \"/caminho/para/pasta\".", "embeddingErrorDefault": "Este erro está relacionado com o sistema de embebimento utilizado para analisar o seu repositório. Verifique a configuração do modelo de embebimento, as chaves de API e tente novamente. Se o problema persistir, considere mudar para um provedor de embebimento diferente na configuração do modelo.", "backToHome": "Voltar ao Início", "exportWiki": "Exportar Wiki", diff --git a/src/messages/ru.json b/src/messages/ru.json index 6a29752b..dfb7d93f 100644 --- a/src/messages/ru.json +++ b/src/messages/ru.json @@ -20,7 +20,7 @@ "home": { "welcome": "Добро пожаловать в DeepWiki-Open", "welcomeTagline": "Документация с поддержкой ИИ для ваших репозиториев кода", - "description": "Создавайте подробную документацию из репозиториев GitHub, GitLab или Bitbucket всего за несколько кликов.", + "description": "Создавайте подробную документацию из репозиториев GitHub, GitLab, Bitbucket или Codeberg всего за несколько кликов.", "quickStart": "Быстрый старт", "enterRepoUrl": "Введите URL репозитория в одном из следующих форматов:", "advancedVisualization": "Продвинутая визуализация с диаграммами Mermaid", @@ -31,7 +31,7 @@ "form": { "repository": "Репозиторий", "configureWiki": "Настроить Wiki", - "repoPlaceholder": "owner/repo или URL GitHub/GitLab/Bitbucket", + "repoPlaceholder": "owner/repo или URL GitHub/GitLab/Bitbucket/Codeberg", "wikiLanguage": "Язык Wiki", "modelOptions": "Настройки модели", "modelProvider": "Поставщик модели", @@ -111,7 +111,7 @@ "cancel": "Отмена", "home": "Главная", "errorTitle": "Ошибка", - "errorMessageDefault": "Пожалуйста, убедитесь, что ваш репозиторий существует и является публичным. Допустимые форматы: \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\" или локальные пути вроде \"C:\\\\path\\\\to\\\\folder\" или \"/path/to/folder\".", + "errorMessageDefault": "Пожалуйста, убедитесь, что ваш репозиторий существует и является публичным. Допустимые форматы: \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", \"https://codeberg.org/owner/repo\" или локальные пути вроде \"C:\\\\path\\\\to\\\\folder\" или \"/path/to/folder\".", "embeddingErrorDefault": "Ошибка связана с системой встраивания документов для анализа репозитория. Проверьте конфигурацию модели встраивания, API-ключи и повторите попытку. Если проблема сохраняется, попробуйте сменить поставщика модели в настройках.", "backToHome": "Назад на главную", "exportWiki": "Экспортировать Wiki", diff --git a/src/messages/vi.json b/src/messages/vi.json index 7ac1933c..73104e83 100644 --- a/src/messages/vi.json +++ b/src/messages/vi.json @@ -20,7 +20,7 @@ "home": { "welcome": "Chào mừng đến với DeepWiki-Open", "welcomeTagline": "Tài liệu hỗ trợ bởi AI cho các repository của bạn", - "description": "Tạo tài liệu từ các repository GitHub, GitLab, hoặc Bitbucket chỉ với vài cú nhấp chuột.", + "description": "Tạo tài liệu từ các repository GitHub, GitLab, Bitbucket hoặc Codeberg chỉ với vài cú nhấp chuột.", "quickStart": "Bắt đầu nhanh", "enterRepoUrl": "Nhập URL repository", "advancedVisualization": "Tùy chỉnh sơ đồ trực quan với Mermaid", @@ -31,7 +31,7 @@ "form": { "repository": "Repository", "configureWiki": "Cấu hình Wiki", - "repoPlaceholder": "owner/repo hoặc URL GitHub/GitLab/Bitbucket", + "repoPlaceholder": "owner/repo hoặc URL GitHub/GitLab/Bitbucket/Codeberg", "wikiLanguage": "Ngôn ngữ Wiki", "modelOptions": "Tùy chọn mô hình", "modelProvider": "Nhà cung cấp mô hình", @@ -102,7 +102,7 @@ "cancel": "Hủy bỏ", "home": "Trang chủ", "errorTitle": "Lỗi", - "errorMessageDefault": "Vui lòng kiểm tra xem repository có tồn tại và công khai hay không. Các định dạng hợp lệ là \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", hoặc các đường dẫn thư mục cục bộ như \"C:\\\\path\\\\to\\\\folder\" hoặc \"/path/to/folder\".", + "errorMessageDefault": "Vui lòng kiểm tra xem repository có tồn tại và công khai hay không. Các định dạng hợp lệ là \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", \"https://codeberg.org/owner/repo\" hoặc các đường dẫn thư mục cục bộ như \"C:\\\\path\\\\to\\\\folder\" hoặc \"/path/to/folder\".", "embeddingErrorDefault": "Lỗi này liên quan đến hệ thống embedding được sử dụng để phân tích repository của bạn. Vui lòng kiểm tra cấu hình mô hình embedding, API keys và thử lại. Nếu vấn đề vẫn tiếp diễn, hãy xem xét chuyển sang nhà cung cấp embedding khác trong cấu hình mô hình.", "backToHome": "Quay lại trang chủ", "exportWiki": "Xuất Wiki", diff --git a/src/messages/zh-tw.json b/src/messages/zh-tw.json index 67a1bd2d..3e0f3d7f 100644 --- a/src/messages/zh-tw.json +++ b/src/messages/zh-tw.json @@ -20,7 +20,7 @@ "home": { "welcome": "歡迎使用 DeepWiki", "welcomeTagline": "為程式碼儲存庫提供 AI 驅動的文件", - "description": "只需一次點擊,即可從 GitHub、GitLab 或 Bitbucket 儲存庫產生全面的文件。", + "description": "只需一次點擊,即可從 GitHub、GitLab、Bitbucket 或 Codeberg 儲存庫產生全面的文件。", "quickStart": "快速開始", "enterRepoUrl": "請以下列格式之一輸入儲存庫 URL:", "advancedVisualization": "使用 Mermaid 圖表進行進階視覺化", @@ -31,7 +31,7 @@ "form": { "repository": "儲存庫", "configureWiki": "設定 Wiki", - "repoPlaceholder": "擁有者/儲存庫或 GitHub/GitLab/Bitbucket URL", + "repoPlaceholder": "擁有者/儲存庫或 GitHub/GitLab/Bitbucket/Codeberg URL", "wikiLanguage": "Wiki 語言", "modelOptions": "模型選項", "modelProvider": "模型提供商", @@ -100,7 +100,7 @@ "cancel": "取消", "home": "首頁", "errorTitle": "錯誤", - "errorMessageDefault": "請檢查您的儲存庫是否存在且為公開儲存庫。有效格式為 \"owner/repo\"、\"https://github.com/owner/repo\"、\"https://gitlab.com/owner/repo\"、\"https://bitbucket.org/owner/repo\",或本機資料夾路徑,如 \"C:\\\\path\\\\to\\\\folder\" 或 \"/path/to/folder\"。", + "errorMessageDefault": "請檢查您的儲存庫是否存在且為公開儲存庫。有效格式為 \"owner/repo\"、\"https://github.com/owner/repo\"、\"https://gitlab.com/owner/repo\"、\"https://bitbucket.org/owner/repo\"、\"https://codeberg.org/owner/repo\",或本機資料夾路徑,如 \"C:\\\\path\\\\to\\\\folder\" 或 \"/path/to/folder\"。", "embeddingErrorDefault": "這個錯誤與用於分析您的儲存庫的文件嵌入系統有關。請檢查您的嵌入模型配置、API 密鑰,並重試。如果問題持續存在,請考慮在模型設置中切換到不同的嵌入提供者。", "backToHome": "返回首頁", "exportWiki": "匯出 Wiki", diff --git a/src/messages/zh.json b/src/messages/zh.json index 10fc7b22..68f08467 100644 --- a/src/messages/zh.json +++ b/src/messages/zh.json @@ -20,7 +20,7 @@ "home": { "welcome": "欢迎使用DeepWiki", "welcomeTagline": "为代码仓库提供AI驱动的文档", - "description": "只需一次点击,即可从GitHub、GitLab或Bitbucket仓库生成全面的文档。", + "description": "只需一次点击,即可从GitHub、GitLab、Bitbucket或Codeberg仓库生成全面的文档。", "quickStart": "快速开始", "enterRepoUrl": "请以下列格式之一输入仓库URL:", "advancedVisualization": "使用Mermaid图表进行高级可视化", @@ -31,7 +31,7 @@ "form": { "repository": "仓库", "configureWiki": "配置Wiki", - "repoPlaceholder": "所有者/仓库或GitHub/GitLab/Bitbucket URL", + "repoPlaceholder": "所有者/仓库或GitHub/GitLab/Bitbucket/Codeberg URL", "wikiLanguage": "Wiki语言", "modelOptions": "模型选项", "modelProvider": "模型提供商", @@ -102,7 +102,7 @@ "cancel": "取消", "home": "首页", "errorTitle": "错误", - "errorMessageDefault": "请检查您的仓库是否存在且为公开仓库。有效格式为\"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", 或本地文件夹路径,如\"C:\\\\path\\\\to\\\\folder\"或\"/path/to/folder\"。", + "errorMessageDefault": "请检查您的仓库是否存在且为公开仓库。有效格式为\"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\"、\"https://codeberg.org/owner/repo\"或本地文件夹路径,如\"C:\\\\path\\\\to\\\\folder\"或\"/path/to/folder\"。", "embeddingErrorDefault": "这个错误与用于分析您的仓库的文件嵌入系统有关。请检查您的嵌入模型配置、API 密钥,并重试。如果问题持续存在,请考虑在模型设置中切换到不同的嵌入提供者。", "backToHome": "返回首页", "exportWiki": "导出Wiki", diff --git a/test/test_extract_repo_name.py b/test/test_extract_repo_name.py index 65a15da0..6d91118e 100644 --- a/test/test_extract_repo_name.py +++ b/test/test_extract_repo_name.py @@ -66,6 +66,14 @@ def test_extract_repo_name_bitbucket_urls(self): assert result == "owner_repo" print("✓ Bitbucket URL tests passed") + + def test_extract_repo_name_codeberg_urls(self): + """Test repository name extraction from Codeberg URLs""" + codeberg_url = "https://codeberg.org/owner/repo" + result = self.db_manager._extract_repo_name_from_url(codeberg_url, "codeberg") + assert result == "owner_repo" + + print("✓ Codeberg URL tests passed") def test_extract_repo_name_local_paths(self): """Test repository name extraction from local paths""" From dcbff7499d3ce801ce715f7a0fef787d4d7a1bd3 Mon Sep 17 00:00:00 2001 From: medenor <60567543+Medenor@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:40:25 +0100 Subject: [PATCH 2/4] Update api/data_pipeline.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/data_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/data_pipeline.py b/api/data_pipeline.py index 0fe796d4..1e9d94d2 100644 --- a/api/data_pipeline.py +++ b/api/data_pipeline.py @@ -711,7 +711,7 @@ def get_codeberg_file_content(repo_url: str, file_path: str, access_token: str = default_branch = repo_info.get('default_branch') or default_branch else: logger.warning("Could not fetch Codeberg repository info, using 'main' as default branch") - except Exception as exc: + except RequestException as exc: logger.warning(f"Error fetching Codeberg repository info: {exc}. Using 'main' as default branch") file_url = f"{api_base}/repos/{encoded_repo_path}/contents/{encoded_file_path}?ref={quote(default_branch, safe='')}" From b9f2f83a234c7299549ea1b08c5a5aed15a8a7d5 Mon Sep 17 00:00:00 2001 From: medenor <60567543+Medenor@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:41:33 +0100 Subject: [PATCH 3/4] Update api/data_pipeline.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/data_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/data_pipeline.py b/api/data_pipeline.py index 1e9d94d2..8f82ed03 100644 --- a/api/data_pipeline.py +++ b/api/data_pipeline.py @@ -725,7 +725,7 @@ def get_codeberg_file_content(repo_url: str, file_path: str, access_token: str = if encoding == 'base64': try: return base64.b64decode(content.encode('utf-8')).decode('utf-8') - except Exception as decode_error: + except (base64.binascii.Error, UnicodeDecodeError) as decode_error: raise ValueError(f"Failed to decode Codeberg file content: {decode_error}") return content elif response.status_code == 404: From e23a90c4580ac4bb93660024d4ad3c78e007eff0 Mon Sep 17 00:00:00 2001 From: medenor <60567543+Medenor@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:41:42 +0100 Subject: [PATCH 4/4] Update api/data_pipeline.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/data_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/data_pipeline.py b/api/data_pipeline.py index 8f82ed03..ddb4e94f 100644 --- a/api/data_pipeline.py +++ b/api/data_pipeline.py @@ -771,7 +771,7 @@ def get_file_content(repo_url: str, file_path: str, repo_type: str = None, acces elif repo_type == "codeberg": return get_codeberg_file_content(repo_url, file_path, access_token) else: - raise ValueError("Unsupported repository type. Only GitHub, GitLab, and Bitbucket are supported.") + raise ValueError("Unsupported repository type. Only GitHub, GitLab, Bitbucket, and Codeberg are supported.") class DatabaseManager: """