diff --git a/src-tauri/src/commands/folders.rs b/src-tauri/src/commands/folders.rs index 875cd61..25e4a37 100644 --- a/src-tauri/src/commands/folders.rs +++ b/src-tauri/src/commands/folders.rs @@ -1066,7 +1066,7 @@ pub async fn git_list_all_branches(path: String) -> Result String::from_utf8_lossy(&output.stdout) .lines() .map(|l| l.trim().to_string()) - .filter(|l| !l.is_empty() && !l.contains("HEAD")) + .filter(|l| !l.is_empty() && !l.contains("HEAD") && l.contains('/')) .collect(), _ => vec![], }; diff --git a/src/components/layout/aux-panel-file-tree-tab.tsx b/src/components/layout/aux-panel-file-tree-tab.tsx index 0014742..d983c7f 100644 --- a/src/components/layout/aux-panel-file-tree-tab.tsx +++ b/src/components/layout/aux-panel-file-tree-tab.tsx @@ -1315,6 +1315,19 @@ export function FileTreeTab() { } }, [compareBranchList, compareFilterKeyword]) + const groupedCompareRemoteBranches = useMemo(() => { + const groups: Record = {} + for (const b of filteredCompareBranches.remote) { + const slashIndex = b.indexOf("/") + const remoteName = slashIndex > 0 ? b.substring(0, slashIndex) : "origin" + if (!groups[remoteName]) groups[remoteName] = [] + groups[remoteName].push(b) + } + return groups + }, [filteredCompareBranches.remote]) + const compareRemoteNames = Object.keys(groupedCompareRemoteBranches) + const hasMultipleCompareRemotes = compareRemoteNames.length > 1 + const directoryGitTreeNodes = useMemo(() => { if (!directoryGitActionTarget) return [] return buildDirectoryGitTree( @@ -2455,21 +2468,58 @@ export function FileTreeTab() { {filteredCompareBranches.remote.length > 0 ? ( - filteredCompareBranches.remote.map((branch) => ( - - )) + hasMultipleCompareRemotes ? ( + compareRemoteNames.map((remoteName) => ( + + + + {remoteName} ({groupedCompareRemoteBranches[remoteName].length}) + + + {groupedCompareRemoteBranches[remoteName].map( + (branch) => ( + + ) + )} + + + )) + ) : ( + filteredCompareBranches.remote.map((branch) => { + const slashIndex = branch.indexOf("/") + const shortName = + slashIndex > 0 + ? branch.substring(slashIndex + 1) + : branch + return ( + + ) + }) + ) ) : (
{t("compareDialog.noMatchingBranches")} diff --git a/src/components/layout/aux-panel-git-log-tab.tsx b/src/components/layout/aux-panel-git-log-tab.tsx index 4a805dc..187db26 100644 --- a/src/components/layout/aux-panel-git-log-tab.tsx +++ b/src/components/layout/aux-panel-git-log-tab.tsx @@ -10,6 +10,8 @@ import { } from "react" import { useTranslations } from "next-intl" import { + ChevronDown, + ChevronRight, ChevronsDownUp, ChevronsUpDown, CircleHelp, @@ -62,15 +64,15 @@ import { } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible" import { Skeleton } from "@/components/ui/skeleton" import { useFolderContext } from "@/contexts/folder-context" import { useWorkspaceContext } from "@/contexts/workspace-context" @@ -527,55 +529,126 @@ function BranchSelector({ refreshing: boolean }) { const t = useTranslations("Folder.gitLogTab.branchSelector") + const [popoverOpen, setPopoverOpen] = useState(false) + const [localOpen, setLocalOpen] = useState(true) + const [remoteOpen, setRemoteOpen] = useState(false) + const groupedRemoteBranches = useMemo(() => { + const groups: Record = {} + for (const b of branchList.remote) { + const slashIndex = b.indexOf("/") + const remoteName = slashIndex > 0 ? b.substring(0, slashIndex) : "origin" + if (!groups[remoteName]) groups[remoteName] = [] + groups[remoteName].push(b) + } + return groups + }, [branchList.remote]) + const remoteNames = Object.keys(groupedRemoteBranches) + const hasMultipleRemotes = remoteNames.length > 1 + + const handleSelect = (branch: string) => { + onBranchChange(branch) + setPopoverOpen(false) + } + + function renderBranchItem( + branch: string, + displayName?: string, + indent = 0 + ) { + const isCurrent = branch === selectedBranch + return ( + + ) + } + return (
- + + + )} + {branchList.remote.length > 0 && ( + + + + {t("remoteBranches")} + + + {hasMultipleRemotes ? ( + remoteNames.map((remoteName) => ( + + + + {remoteName} ({groupedRemoteBranches[remoteName].length}) + + + {groupedRemoteBranches[remoteName].map((branch) => + renderBranchItem( + branch, + branch.substring(remoteName.length + 1), + 3 + ) + )} + + + )) + ) : ( + branchList.remote.map((branch) => { + const slashIndex = branch.indexOf("/") + const shortName = + slashIndex > 0 ? branch.substring(slashIndex + 1) : branch + return renderBranchItem(branch, shortName, 1) + }) + )} + + + )} +
+ +
) @@ -412,7 +417,7 @@ export function BranchDropdown({ onPointerLeave={(e) => e.preventDefault()} > - {b} + {label} {remoteName} ({groupedRemoteBranches[remoteName].length}) - + {groupedRemoteBranches[remoteName].map((b) => - renderBranchItem(b, true) + renderBranchItem( + b, + true, + b.substring(remoteName.length + 1) + ) )} )) ) : ( - branchList.remote.map((b) => renderBranchItem(b, true)) + branchList.remote.map((b) => { + const slashIndex = b.indexOf("/") + const shortName = + slashIndex > 0 ? b.substring(slashIndex + 1) : b + return renderBranchItem(b, true, shortName) + }) )}