feat(desktop): add interactive git branch indicator to chat bottom bar (#11290)
Signed-off-by: Abhijay Jain <Abhijay007j@gmail.com> Signed-off-by: Douwe Osinga <douwe.osinga@gmail.com> Co-authored-by: Douwe Osinga <douwe.osinga@gmail.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { ChatState } from '../types/chatState';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { LocalMessageStorage } from '../utils/localMessageStorage';
|
||||
import { DirSwitcher } from './bottom_menu/DirSwitcher';
|
||||
import { GitBranchIndicator } from './GitBranchIndicator';
|
||||
import ModelsBottomBar from './settings/models/bottom_bar/ModelsBottomBar';
|
||||
import { BottomMenuExtensionSelection } from './bottom_menu/BottomMenuExtensionSelection';
|
||||
import { cn } from '../utils';
|
||||
@@ -1683,6 +1684,10 @@ export default function ChatInput({
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isBottomBarNarrow && currentWorkingDir && (
|
||||
<GitBranchIndicator dir={currentWorkingDir} className="ml-1" />
|
||||
)}
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { GitBranch, Check, Search } from 'lucide-react';
|
||||
import { toastError } from '../toasts';
|
||||
import { cn } from '../utils';
|
||||
import { defineMessages, useIntl } from '../i18n';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from './ui/dropdown-menu';
|
||||
|
||||
const i18n = defineMessages({
|
||||
noBranchesFound: {
|
||||
id: 'gitBranchIndicator.noBranchesFound',
|
||||
defaultMessage: 'No branches found',
|
||||
},
|
||||
searchBranches: {
|
||||
id: 'gitBranchIndicator.searchBranches',
|
||||
defaultMessage: 'Search branches…',
|
||||
},
|
||||
failedToSwitch: {
|
||||
id: 'gitBranchIndicator.failedToSwitch',
|
||||
defaultMessage: 'Failed to switch to {branch}',
|
||||
},
|
||||
uncommittedChanges: {
|
||||
id: 'gitBranchIndicator.uncommittedChanges',
|
||||
defaultMessage: 'You may have uncommitted changes.',
|
||||
},
|
||||
});
|
||||
|
||||
export const GitBranchIndicator: React.FC<{ dir: string; className?: string }> = ({
|
||||
dir,
|
||||
className,
|
||||
}) => {
|
||||
const [branch, setBranch] = useState<string | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [branches, setBranches] = useState<string[]>([]);
|
||||
const [search, setSearch] = useState('');
|
||||
const [switching, setSwitching] = useState(false);
|
||||
const intl = useIntl();
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dir) return;
|
||||
let cancelled = false;
|
||||
const refresh = () => {
|
||||
window.electron
|
||||
.getGitBranchInfo(dir)
|
||||
.then((info) => {
|
||||
if (!cancelled) setBranch(info?.branch ?? null);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
refresh();
|
||||
window.addEventListener('focus', refresh);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.removeEventListener('focus', refresh);
|
||||
};
|
||||
}, [dir]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
let cancelled = false;
|
||||
setBranches([]);
|
||||
setSearch('');
|
||||
Promise.all([window.electron.getGitBranchInfo(dir), window.electron.listGitBranches(dir)])
|
||||
.then(([info, list]) => {
|
||||
if (!cancelled) {
|
||||
setBranch(info?.branch ?? null);
|
||||
setBranches(list);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
setTimeout(() => searchRef.current?.focus(), 50);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, dir]);
|
||||
|
||||
if (!branch) return null;
|
||||
|
||||
const filtered = branches.filter((b) => b.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
const handleSwitch = async (target: string) => {
|
||||
if (target === branch || switching) return;
|
||||
setSwitching(true);
|
||||
setOpen(false);
|
||||
const result = await window.electron
|
||||
.switchGitBranch(dir, target)
|
||||
.catch((err: Error) => ({ success: false, error: err.message }));
|
||||
if (result.success) {
|
||||
setBranch(target);
|
||||
} else {
|
||||
toastError({
|
||||
title: intl.formatMessage(i18n.failedToSwitch, { branch: target }),
|
||||
msg: result.error ?? intl.formatMessage(i18n.uncommittedChanges),
|
||||
});
|
||||
}
|
||||
setSwitching(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'text-text-primary/70 text-xs flex items-center transition-colors',
|
||||
switching ? 'opacity-50' : 'hover:cursor-pointer hover:text-text-primary',
|
||||
className
|
||||
)}
|
||||
disabled={switching}
|
||||
>
|
||||
<GitBranch className="mr-1" size={14} />
|
||||
<span className="max-w-[100px] truncate whitespace-nowrap">{branch}</span>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="top" align="start" className="w-64 p-0 overflow-hidden">
|
||||
<div className="overflow-y-auto p-1 max-h-52 space-y-0.5">
|
||||
{filtered.length === 0 && (
|
||||
<div className="px-2 py-1.5 text-sm text-text-primary/50">
|
||||
{intl.formatMessage(i18n.noBranchesFound)}
|
||||
</div>
|
||||
)}
|
||||
{filtered.map((b) => (
|
||||
<DropdownMenuItem key={b} onSelect={() => void handleSwitch(b)}>
|
||||
<span className="flex-1 truncate">{b}</span>
|
||||
{b === branch && <Check className="ml-auto h-4 w-4 flex-shrink-0" />}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="px-2 py-1.5 flex items-center gap-1.5">
|
||||
<Search className="h-3.5 w-3.5 text-text-muted flex-shrink-0" />
|
||||
<input
|
||||
ref={searchRef}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key.length === 1) e.stopPropagation();
|
||||
}}
|
||||
placeholder={intl.formatMessage(i18n.searchBranches)}
|
||||
aria-label={intl.formatMessage(i18n.searchBranches)}
|
||||
className="flex-1 bg-transparent text-sm text-text-primary placeholder:text-text-primary/50 outline-none"
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Zuletzt verwendete Verzeichnisse"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Wechsel zu {branch} fehlgeschlagen"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Keine Branches gefunden"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Branches durchsuchen…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Möglicherweise gibt es noch nicht committete Änderungen."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Akzeptieren"
|
||||
},
|
||||
|
||||
@@ -1259,6 +1259,18 @@
|
||||
"externalBackendSection.workingDirPlaceholder": {
|
||||
"defaultMessage": "/home/goose/workspace"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Failed to switch to {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "No branches found"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Search branches…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "You may have uncommitted changes."
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Directorios recientes"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "No se pudo cambiar a {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "No se encontraron ramas"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Buscar ramas…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Puede que tengas cambios sin confirmar."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Aceptar"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Répertoires récents"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Impossible de basculer vers {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Aucune branche trouvée"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Rechercher des branches…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Vous avez peut-être des modifications non validées."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Accepter"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "हाल की निर्देशिकाएँ"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "{branch} पर स्विच नहीं किया जा सका"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "कोई ब्रांच नहीं मिली"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "ब्रांच खोजें…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "आपके कुछ बदलाव कमिट नहीं हुए हो सकते हैं।"
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "स्वीकार करो"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Direktori terbaru"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Gagal beralih ke {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Tidak ada branch yang ditemukan"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Cari branch…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Anda mungkin memiliki perubahan yang belum di-commit."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Terima"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Cartelle recenti"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Impossibile passare a {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Nessun branch trovato"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Cerca branch…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Potresti avere modifiche non incluse in un commit."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Accetta"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "最近のディレクトリ"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "{branch} への切り替えに失敗しました"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "ブランチが見つかりません"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "ブランチを検索…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "コミットされていない変更がある可能性があります。"
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "承諾"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "최근 디렉터리"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "{branch} 브랜치로 전환하지 못했습니다"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "브랜치를 찾을 수 없습니다"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "브랜치 검색…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "커밋되지 않은 변경 사항이 있을 수 있습니다."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "수락"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Direktori terkini"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Gagal beralih ke {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Tiada cabang ditemui"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Cari cabang…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Anda mungkin mempunyai perubahan yang belum dikomit."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Terima"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Diretórios recentes"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Falha ao mudar para {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Nenhum ramo encontrado"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Pesquisar ramos…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Pode haver alterações sem commit."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Aceitar"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Недавние каталоги"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Не удалось переключиться на {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Ветки не найдены"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Поиск веток…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Возможно, у вас есть незакоммиченные изменения."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Принять"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Son dizinler"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "{branch} dalına geçilemedi"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Dal bulunamadı"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Dal ara…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Commit edilmemiş değişiklikleriniz olabilir."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Kabul et"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "Thư mục gần đây"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "Không thể chuyển sang {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "Không tìm thấy nhánh"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "Tìm kiếm nhánh…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "Bạn có thể có các thay đổi chưa commit."
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "Chấp nhận"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "最近的目录"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "无法切换到 {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "未找到分支"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "搜索分支…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "你可能有尚未提交的更改。"
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "接受"
|
||||
},
|
||||
|
||||
@@ -932,6 +932,18 @@
|
||||
"dirSwitcher.recentDirectories": {
|
||||
"defaultMessage": "最近的目錄"
|
||||
},
|
||||
"gitBranchIndicator.failedToSwitch": {
|
||||
"defaultMessage": "無法切換至 {branch}"
|
||||
},
|
||||
"gitBranchIndicator.noBranchesFound": {
|
||||
"defaultMessage": "找不到分支"
|
||||
},
|
||||
"gitBranchIndicator.searchBranches": {
|
||||
"defaultMessage": "搜尋分支…"
|
||||
},
|
||||
"gitBranchIndicator.uncommittedChanges": {
|
||||
"defaultMessage": "你可能有尚未提交的變更。"
|
||||
},
|
||||
"elicitationRequest.accept": {
|
||||
"defaultMessage": "接受"
|
||||
},
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
updateTrayMenu,
|
||||
} from './utils/autoUpdater';
|
||||
import { UPDATES_ENABLED } from './updates';
|
||||
import './utils/gitBranchIpc';
|
||||
import './utils/recipeHash';
|
||||
import type { GooseApp } from './types/apps';
|
||||
import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
|
||||
|
||||
@@ -180,6 +180,9 @@ type ElectronAPI = {
|
||||
addRecentDir: (dir: string) => Promise<boolean>;
|
||||
listRecentDirs: () => Promise<string[]>;
|
||||
listGitWorktreeDirs: (dir: string) => Promise<string[]>;
|
||||
getGitBranchInfo: (dir: string) => Promise<{ branch: string } | null>;
|
||||
listGitBranches: (dir: string) => Promise<string[]>;
|
||||
switchGitBranch: (dir: string, branch: string) => Promise<{ success: boolean; error?: string }>;
|
||||
};
|
||||
|
||||
type AppConfigAPI = {
|
||||
@@ -339,6 +342,10 @@ const electronAPI: ElectronAPI = {
|
||||
addRecentDir: (dir: string) => ipcRenderer.invoke('add-recent-dir', dir),
|
||||
listRecentDirs: () => ipcRenderer.invoke('list-recent-dirs'),
|
||||
listGitWorktreeDirs: (dir: string) => ipcRenderer.invoke('list-git-worktree-dirs', dir),
|
||||
getGitBranchInfo: (dir: string) => ipcRenderer.invoke('get-git-branch-info', dir),
|
||||
listGitBranches: (dir: string) => ipcRenderer.invoke('list-git-branches', dir),
|
||||
switchGitBranch: (dir: string, branch: string) =>
|
||||
ipcRenderer.invoke('switch-git-branch', dir, branch),
|
||||
};
|
||||
|
||||
function getAppLocale(): unknown {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { execFile } from 'child_process';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
const gitArgs = (dir: string, args: string[]) => [
|
||||
'-c',
|
||||
'safe.bareRepository=explicit',
|
||||
'-c',
|
||||
'core.fsmonitor=false',
|
||||
'-C',
|
||||
dir,
|
||||
...args,
|
||||
];
|
||||
|
||||
const git = (dir: string, args: string[], timeout = 3000) =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
execFile('git', gitArgs(dir, args), { timeout }, (error, stdout) => {
|
||||
if (error) reject(error);
|
||||
else resolve(stdout.trim());
|
||||
});
|
||||
});
|
||||
|
||||
const getCurrentBranch = async (dir: string) => {
|
||||
try {
|
||||
const ref = await git(dir, ['symbolic-ref', 'HEAD']);
|
||||
return ref.startsWith('refs/heads/') ? ref.slice('refs/heads/'.length) : ref;
|
||||
} catch {
|
||||
return git(dir, ['rev-parse', '--short', 'HEAD']).catch(() => null);
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.handle(
|
||||
'get-git-branch-info',
|
||||
async (_event, dir: string): Promise<{ branch: string } | null> => {
|
||||
if (!dir?.trim()) return null;
|
||||
|
||||
const branch = await getCurrentBranch(dir);
|
||||
return branch ? { branch } : null;
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle('list-git-branches', async (_event, dir: string): Promise<string[]> => {
|
||||
if (!dir?.trim()) return [];
|
||||
|
||||
try {
|
||||
const branches = await git(dir, [
|
||||
'for-each-ref',
|
||||
'refs/heads/',
|
||||
'--format=%(refname:lstrip=2)',
|
||||
]);
|
||||
return branches.split('\n').filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
'switch-git-branch',
|
||||
async (_event, dir: string, branch: string): Promise<{ success: boolean; error?: string }> => {
|
||||
if (!dir?.trim() || !branch?.trim()) return { success: false };
|
||||
|
||||
try {
|
||||
await git(dir, ['checkout', branch], 30000);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const currentBranch = await getCurrentBranch(dir);
|
||||
if (currentBranch === branch) return { success: true };
|
||||
|
||||
const gitError = error as Error & { stderr?: string };
|
||||
return { success: false, error: gitError.stderr?.toString() || gitError.message };
|
||||
}
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user