From 696d06beeb3e2e0fed0a435c6679e24f7aa92ba3 Mon Sep 17 00:00:00 2001 From: bkellam Date: Wed, 19 Nov 2025 18:35:03 -0800 Subject: [PATCH] branch handling --- .../app/[domain]/components/pathHeader.tsx | 2 +- .../search/components/searchResultsPage.tsx | 12 +++++++++--- .../searchResultsPanel/fileMatchContainer.tsx | 2 +- .../components/searchResultsPanel/index.tsx | 4 ++-- .../app/[domain]/search/useStreamedSearch.ts | 1 - packages/web/src/features/search/query.ts | 13 ++++--------- packages/web/src/features/search/searchApi.ts | 19 ++++++++++++++++--- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/web/src/app/[domain]/components/pathHeader.tsx b/packages/web/src/app/[domain]/components/pathHeader.tsx index d65d2c35..7d373b2e 100644 --- a/packages/web/src/app/[domain]/components/pathHeader.tsx +++ b/packages/web/src/app/[domain]/components/pathHeader.tsx @@ -233,7 +233,7 @@ export const PathHeader = ({ }} > @ - {`${branchDisplayName}`} + {`${branchDisplayName.replace(/^refs\/(heads|tags)\//, '')}`}

)} ยท diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx index 4e8982cc..285def39 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx @@ -12,6 +12,7 @@ import { import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { RepositoryInfo, SearchResultFile, SearchStats } from "@/features/search/types"; +import useCaptureEvent from "@/hooks/useCaptureEvent"; import { useDomain } from "@/hooks/useDomain"; import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; import { useSearchHistory } from "@/hooks/useSearchHistory"; @@ -32,7 +33,6 @@ import { CodePreviewPanel } from "./codePreviewPanel"; import { FilterPanel } from "./filterPanel"; import { useFilteredMatches } from "./filterPanel/useFilterMatches"; import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; interface SearchResultsPageProps { searchQuery: string; @@ -156,6 +156,13 @@ export const SearchResultsPage = ({ router.push(url); }, [maxMatchCount, router, searchQuery, domain]); + // Look for any files that are not on the default branch. + const isBranchFilteringEnabled = useMemo(() => { + return files.some((file) => { + return file.branches?.some((branch) => branch !== 'HEAD') ?? false; + }); + }, [files]); + return (
{/* TopBar */} @@ -189,8 +196,7 @@ export const SearchResultsPage = ({ isStreaming={isStreaming} searchStats={stats} isMoreResultsButtonVisible={!isExhaustive} - // @todo: handle branch filtering - isBranchFilteringEnabled={false} + isBranchFilteringEnabled={isBranchFilteringEnabled} /> )}
diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx index b10d656a..e7740ca9 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx @@ -75,7 +75,7 @@ export const FileMatchContainer = ({ } return `${branches[0]}${branches.length > 1 ? ` +${branches.length - 1}` : ''}`; - }, [isBranchFilteringEnabled, branches]); + }, [branches, isBranchFilteringEnabled]); const repo = useMemo(() => { return repoInfo[file.repositoryId]; diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx index aa5efdf8..5d199cc0 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx @@ -88,7 +88,7 @@ export const SearchResultsPanel = forwardRef { virtualizer.scrollToIndex(0); - }, [fileMatches.length, virtualizer]); + }, [virtualizer]); // Expose the resetScroll function to parent components useImperativeHandle(ref, () => ({ @@ -121,7 +121,7 @@ export const SearchResultsPanel = forwardRef { }; export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegexEnabled, isCaseSensitivityEnabled }: SearchRequest) => { - const [state, setState] = useState<{ isStreaming: boolean, isExhaustive: boolean, diff --git a/packages/web/src/features/search/query.ts b/packages/web/src/features/search/query.ts index 2c0a831f..3505e451 100644 --- a/packages/web/src/features/search/query.ts +++ b/packages/web/src/features/search/query.ts @@ -61,7 +61,6 @@ export const transformLezerTreeToZoektGrpcQuery = async ({ isRegexEnabled: boolean; onExpandSearchContext: (contextName: string) => Promise; }): Promise => { - const transformNode = async (node: SyntaxNode): Promise => { switch (node.type.id) { case Program: { @@ -200,7 +199,7 @@ export const transformLezerTreeToZoektGrpcQuery = async ({ query: "substring" }; - + case LangExpr: return { language: { @@ -277,11 +276,11 @@ export const transformLezerTreeToZoektGrpcQuery = async ({ } case ForkExpr: { const rawValue = value.toLowerCase(); - + if (!isForkValue(rawValue)) { throw new Error(`Invalid fork value: ${rawValue}. Expected 'yes', 'no', or 'only'`); } - + const flags: ('FLAG_ONLY_FORKS' | 'FLAG_NO_FORKS')[] = []; if (rawValue === 'yes') { @@ -336,12 +335,8 @@ const getChildren = (node: SyntaxNode): SyntaxNode[] => { const children: SyntaxNode[] = []; let child = node.firstChild; while (child) { - // Skip certain node types that are just structural - if (!["(", ")", "or"].includes(child.type.name)) { - children.push(child); - } + children.push(child); child = child.nextSibling; } return children; } - diff --git a/packages/web/src/features/search/searchApi.ts b/packages/web/src/features/search/searchApi.ts index 974be894..b6630f7d 100644 --- a/packages/web/src/features/search/searchApi.ts +++ b/packages/web/src/features/search/searchApi.ts @@ -18,6 +18,7 @@ import path from 'path'; import { parseQueryIntoLezerTree, transformLezerTreeToZoektGrpcQuery } from './query'; import { RepositoryInfo, SearchRequest, SearchResponse, SearchResultFile, SearchStats, SourceRange, StreamedSearchResponse } from "./types"; import { FlushReason as ZoektFlushReason } from "@/proto/zoekt/webserver/v1/FlushReason"; +import { RevisionExpr } from "@sourcebot/query-language"; const logger = createLogger("searchApi"); @@ -454,18 +455,30 @@ const createZoektSearchRequest = async ({ }, }); + // Find if there are any `rev:` filters in the query. + let containsRevExpression = false; + tree.iterate({ + enter: (node) => { + if (node.type.id === RevisionExpr) { + containsRevExpression = true; + // false to stop the iteration. + return false; + } + } + }); + const zoektSearchRequest: ZoektGrpcSearchRequest = { query: { and: { children: [ zoektQuery, - // @todo: handle branch filtering. - { + // If the query does not contain a `rev:` filter, we default to searching `HEAD`. + ...(!containsRevExpression ? [{ branch: { pattern: 'HEAD', exact: true, } - } + }] : []), ] } },