feat(git): support nested git repositories in the Git tab

When the project root is not itself a git repository, discover nested
repositories (depth- and visit-capped readdir walk via a new
/api/fs/git-dirs route), auto-select the first one, and show a repository
picker next to the branch dropdown to switch. Selections persist per
runtime and root; discovery failure is a distinct marker with a retry
action, never an empty success.
This commit is contained in:
jaygupta17
2026-08-08 19:08:42 +05:30
parent 4faf660a38
commit 9c9c28a552
20 changed files with 989 additions and 191 deletions
+17
View File
@@ -115,6 +115,23 @@ export async function checkIsGitRepository(directory: string): Promise<boolean>
}
}
export async function listGitDirectories(root: string): Promise<string[]> {
const response = await runtimeFetch('/api/fs/git-dirs', { query: { path: root } });
if (!response.ok) {
throw new Error(`Failed to list git directories: ${response.statusText}`);
}
const data = await response.json();
if (!data || !Array.isArray(data.repositories)) {
throw new Error('Unexpected git directories response');
}
return data.repositories
.map((entry: unknown) => {
const path = entry && typeof entry === 'object' && 'path' in entry ? (entry as { path?: unknown }).path : undefined;
return typeof path === 'string' && path.trim() ? path.trim() : null;
})
.filter((path: string | null): path is string => path !== null);
}
export async function getGitStatus(directory: string, options?: { mode?: 'light' }): Promise<GitStatus> {
const mode = options?.mode;
const runtimeKey = getRuntimeKey();
+4
View File
@@ -859,6 +859,10 @@ export const dict = {
'gitView.empty.worktreeFeaturesUnavailable': 'Worktree-Funktionen sind in diesem Arbeitsbereichsmodus nicht verfügbar.',
'gitView.empty.worktreeSetupDescription': 'Arbeitstruktur-Einrichtung wird abgeschlossen und Repository-Zustand wird vorbereitet.',
'gitView.empty.worktreeSetupInProgress': 'Worktree-Einrichtung läuft',
'gitView.empty.discoveringRepositories': 'Suche nach Git-Repositories...',
'gitView.empty.discoverFailed': 'Git-Repositories konnten nicht durchsucht werden',
'gitView.empty.retryDiscovery': 'Erneut versuchen',
'gitView.empty.selectRepositoryPlaceholder': 'Repository auswählen...',
'worktree.bootstrap.toast.failed': 'Worktree-Einrichtung fehlgeschlagen',
'worktree.bootstrap.toast.failedDescription': 'Die Worktree wurde erstellt, aber die Hintergrund-Einrichtung wurde nicht abgeschlossen.',
'worktree.bootstrap.toast.timeoutDescription': 'Die Worktree wurde erstellt, aber die Hintergrund-Einrichtung hat ein Timeout.',
+4
View File
@@ -923,6 +923,10 @@ export const dict = {
'gitView.empty.worktreeFeaturesUnavailable': 'Worktree features are unavailable in this workspace mode.',
'gitView.empty.worktreeSetupDescription': 'Finishing worktree setup and preparing repository state.',
'gitView.empty.worktreeSetupInProgress': 'Worktree setup in progress',
'gitView.empty.discoveringRepositories': 'Looking for Git repositories...',
'gitView.empty.discoverFailed': 'Could not scan for Git repositories',
'gitView.empty.retryDiscovery': 'Retry',
'gitView.empty.selectRepositoryPlaceholder': 'Select a repository...',
'worktree.bootstrap.toast.failed': 'Worktree setup failed',
'worktree.bootstrap.toast.failedDescription': 'The worktree was created, but background setup did not finish.',
'worktree.bootstrap.toast.timeoutDescription': 'The worktree was created, but background setup timed out.',
+4
View File
@@ -924,6 +924,10 @@ export const dict: Record<I18nKey, string> = {
"gitView.empty.worktreeFeaturesUnavailable": "Las características de worktree no están disponibles en este modo de espacio de trabajo.",
"gitView.empty.worktreeSetupDescription": "Finalizando la configuración de worktree y preparando el estado del repositorio.",
"gitView.empty.worktreeSetupInProgress": "Configuración de worktree en progreso",
"gitView.empty.discoveringRepositories": "Buscando repositorios de Git...",
"gitView.empty.discoverFailed": "No se pudo escanear en busca de repositorios de Git",
"gitView.empty.retryDiscovery": "Reintentar",
"gitView.empty.selectRepositoryPlaceholder": "Selecciona un repositorio...",
"worktree.bootstrap.toast.failed": "Error al configurar el worktree",
"worktree.bootstrap.toast.failedDescription": "El worktree se creó, pero la configuración en segundo plano no terminó.",
"worktree.bootstrap.toast.timeoutDescription": "El worktree se creó, pero la configuración en segundo plano agotó el tiempo de espera.",
+4
View File
@@ -751,6 +751,10 @@ export const dict = {
'gitView.empty.worktreeFeaturesUnavailable': 'Les fonctionnalités Worktree ne sont pas disponibles dans ce mode despace de travail.',
'gitView.empty.worktreeSetupDescription': 'Termine la configuration du worktree et prépare l\'état du dépôt.',
'gitView.empty.worktreeSetupInProgress': 'Configuration de worktree en cours',
'gitView.empty.discoveringRepositories': 'Recherche des dépôts Git...',
'gitView.empty.discoverFailed': 'Impossible danalyser les dépôts Git',
'gitView.empty.retryDiscovery': 'Réessayer',
'gitView.empty.selectRepositoryPlaceholder': 'Sélectionnez un dépôt...',
'gitView.gitmoji.empty': 'Aucun gitmoji trouvé',
'gitView.gitmoji.searchPlaceholder': 'Rechercher des gitmoji...',
'gitView.gitmoji.title': 'Insérer un gitmoji',
+4
View File
@@ -920,6 +920,10 @@ export const dict: Record<I18nKey, string> = {
'gitView.empty.worktreeFeaturesUnavailable': 'このワークスペースモードではワークツリー機能は利用できません。',
'gitView.empty.worktreeSetupDescription': 'ワークツリーのセットアップを完了し、リポジトリ状態を準備中。',
'gitView.empty.worktreeSetupInProgress': 'ワークツリーのセットアップ進行中',
'gitView.empty.discoveringRepositories': 'Git リポジトリを検索しています...',
'gitView.empty.discoverFailed': 'Git リポジトリを検索できませんでした',
'gitView.empty.retryDiscovery': '再試行',
'gitView.empty.selectRepositoryPlaceholder': 'リポジトリを選択...',
'worktree.bootstrap.toast.failed': 'ワークツリーのセットアップに失敗しました',
'worktree.bootstrap.toast.failedDescription': 'ワークツリーは作成されましたが、バックグラウンドセットアップが完了しませんでした。',
'worktree.bootstrap.toast.timeoutDescription': 'ワークツリーは作成されましたが、バックグラウンドセットアップがタイムアウトしました。',
+4
View File
@@ -924,6 +924,10 @@ export const dict: Record<I18nKey, string> = {
'gitView.empty.worktreeFeaturesUnavailable': '이 워크스페이스 모드에서는 워크트리 기능을 사용할 수 없습니다.',
'gitView.empty.worktreeSetupDescription': '워크트리 설정을 마치고 레포지토리 상태를 준비하고 있습니다.',
'gitView.empty.worktreeSetupInProgress': '워크트리 설정 중',
'gitView.empty.discoveringRepositories': 'Git 저장소를 찾는 중...',
'gitView.empty.discoverFailed': 'Git 저장소를 검색할 수 없습니다',
'gitView.empty.retryDiscovery': '다시 시도',
'gitView.empty.selectRepositoryPlaceholder': '저장소 선택...',
'worktree.bootstrap.toast.failed': '워크트리 설정 실패',
'worktree.bootstrap.toast.failedDescription': '워크트리는 생성되었지만 백그라운드 설정이 완료되지 않았습니다.',
'worktree.bootstrap.toast.timeoutDescription': '워크트리는 생성되었지만 백그라운드 설정 시간이 초과되었습니다.',
+4
View File
@@ -1958,6 +1958,10 @@ export const dict: Record<I18nKey, string> = {
'gitView.empty.worktreeFeaturesUnavailable': 'Worktree features are unavailable in this workspace mode.',
'gitView.empty.worktreeSetupDescription': 'Finishing worktree setup and preparing repository state.',
'gitView.empty.worktreeSetupInProgress': 'Worktree setup in progress',
'gitView.empty.discoveringRepositories': 'Szukanie repozytoriów Git...',
'gitView.empty.discoverFailed': 'Nie udało się przeskanować repozytoriów Git',
'gitView.empty.retryDiscovery': 'Ponów',
'gitView.empty.selectRepositoryPlaceholder': 'Wybierz repozytorium...',
'worktree.bootstrap.toast.failed': 'Konfiguracja drzewa pracy nie powiodła się',
'worktree.bootstrap.toast.failedDescription': 'Drzewo pracy zostało utworzone, ale konfiguracja w tle nie została ukończona.',
'worktree.bootstrap.toast.timeoutDescription': 'Drzewo pracy zostało utworzone, ale konfiguracja w tle przekroczyła limit czasu.',
@@ -924,6 +924,10 @@ export const dict: Record<I18nKey, string> = {
"gitView.empty.worktreeFeaturesUnavailable": "Os recursos de worktree não estão disponíveis neste modo de workspace.",
"gitView.empty.worktreeSetupDescription": "Finalizando a configuração de worktree e preparando o status do repositório.",
"gitView.empty.worktreeSetupInProgress": "Configuração de worktree em andamento",
"gitView.empty.discoveringRepositories": "Procurando repositórios Git...",
"gitView.empty.discoverFailed": "Não foi possível verificar os repositórios Git",
"gitView.empty.retryDiscovery": "Tentar novamente",
"gitView.empty.selectRepositoryPlaceholder": "Selecione um repositório...",
"worktree.bootstrap.toast.failed": "Falha na configuração do worktree",
"worktree.bootstrap.toast.failedDescription": "O worktree foi criado, mas a configuração em segundo plano não terminou.",
"worktree.bootstrap.toast.timeoutDescription": "O worktree foi criado, mas a configuração em segundo plano atingiu o tempo limite.",
+4
View File
@@ -924,6 +924,10 @@ export const dict: Record<I18nKey, string> = {
"gitView.empty.worktreeFeaturesUnavailable": "У цьому режимі робочої області функції worktree недоступні.",
"gitView.empty.worktreeSetupDescription": "Завершення налаштування worktree та підготовка стану сховища.",
"gitView.empty.worktreeSetupInProgress": "Виконується налаштування worktree",
"gitView.empty.discoveringRepositories": "Пошук репозиторіїв Git...",
"gitView.empty.discoverFailed": "Не вдалося просканувати репозиторії Git",
"gitView.empty.retryDiscovery": "Повторити",
"gitView.empty.selectRepositoryPlaceholder": "Виберіть репозиторій...",
"worktree.bootstrap.toast.failed": "Не вдалося налаштувати worktree",
"worktree.bootstrap.toast.failedDescription": "Worktree створено, але фонове налаштування не завершилося.",
"worktree.bootstrap.toast.timeoutDescription": "Worktree створено, але час очікування фонового налаштування минув.",
@@ -924,6 +924,10 @@ export const dict: Record<I18nKey, string> = {
'gitView.empty.worktreeFeaturesUnavailable': '当前工作区模式下,工作树功能不可用。',
'gitView.empty.worktreeSetupDescription': '正在完成工作树设置并准备仓库状态。',
'gitView.empty.worktreeSetupInProgress': '工作树设置进行中',
'gitView.empty.discoveringRepositories': '正在查找 Git 仓库...',
'gitView.empty.discoverFailed': '无法扫描 Git 仓库',
'gitView.empty.retryDiscovery': '重试',
'gitView.empty.selectRepositoryPlaceholder': '选择仓库...',
'worktree.bootstrap.toast.failed': '工作树设置失败',
'worktree.bootstrap.toast.failedDescription': '工作树已创建,但后台设置未完成。',
'worktree.bootstrap.toast.timeoutDescription': '工作树已创建,但后台设置超时。',
@@ -936,6 +936,10 @@ export const dict: Record<I18nKey, string> = {
'gitView.empty.worktreeFeaturesUnavailable': '目前工作區模式下,worktree 功能無法使用。',
'gitView.empty.worktreeSetupDescription': '正在完成 worktree 設定並準備儲存庫狀態。',
'gitView.empty.worktreeSetupInProgress': 'worktree 設定進行中',
'gitView.empty.discoveringRepositories': '正在尋找 Git 儲存庫...',
'gitView.empty.discoverFailed': '無法掃描 Git 儲存庫',
'gitView.empty.retryDiscovery': '重試',
'gitView.empty.selectRepositoryPlaceholder': '選擇儲存庫...',
'worktree.bootstrap.toast.failed': 'worktree 設定失敗',
'worktree.bootstrap.toast.failedDescription': 'worktree 已建立,但背景設定未完成。',
'worktree.bootstrap.toast.timeoutDescription': 'worktree 已建立,但背景設定逾時。',