From 5135eca2ae82499917e649e6ac850c3e06c63237 Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 30 Jan 2026 20:58:08 -0800 Subject: [PATCH 1/5] api reorg --- .../[...path]/components/codePreviewPanel.tsx | 2 +- .../components/pureTreePreviewPanel.tsx | 4 +- .../[...path]/components/treePreviewPanel.tsx | 2 +- .../components/fileSearchCommandDialog.tsx | 4 +- .../components/fileTreeItemComponent.tsx | 2 +- .../browse}/components/fileTreeItemIcon.tsx | 2 +- .../browse}/components/fileTreePanel.tsx | 2 +- .../browse}/components/pureFileTreePanel.tsx | 2 +- .../web/src/app/[domain]/browse/layout.tsx | 2 +- packages/web/src/app/api/(client)/client.ts | 8 +- .../web/src/app/api/(server)/commits/route.ts | 2 +- .../web/src/app/api/(server)/files/route.ts | 3 +- .../web/src/app/api/(server)/source/route.ts | 2 +- .../web/src/app/api/(server)/tree/route.ts | 4 +- .../review-agent/nodes/fetchFileContent.ts | 3 +- packages/web/src/features/chat/agent.ts | 2 +- packages/web/src/features/chat/tools.ts | 3 +- packages/web/src/features/fileTree/api.ts | 194 ------------------ packages/web/src/features/fileTree/types.ts | 55 ----- .../{search => git}/dateUtils.test.ts | 0 .../src/features/{search => git}/dateUtils.ts | 0 .../web/src/features/git/getFileSourceApi.ts | 90 ++++++++ packages/web/src/features/git/getFilesApi.ts | 66 ++++++ .../src/features/git/getFolderContentsApi.ts | 78 +++++++ packages/web/src/features/git/getTreeApi.ts | 90 ++++++++ packages/web/src/features/git/index.ts | 6 + .../listCommitsApi.test.ts} | 2 +- .../gitApi.ts => git/listCommitsApi.ts} | 0 packages/web/src/features/git/types.ts | 24 +++ .../features/{fileTree => git}/utils.test.ts | 1 + .../src/features/{fileTree => git}/utils.ts | 37 ++-- .../web/src/features/search/fileSourceApi.ts | 70 ------- packages/web/src/features/search/types.ts | 21 -- 33 files changed, 399 insertions(+), 384 deletions(-) rename packages/web/src/{features/fileTree => app/[domain]/browse}/components/fileTreeItemComponent.tsx (98%) rename packages/web/src/{features/fileTree => app/[domain]/browse}/components/fileTreeItemIcon.tsx (93%) rename packages/web/src/{features/fileTree => app/[domain]/browse}/components/fileTreePanel.tsx (99%) rename packages/web/src/{features/fileTree => app/[domain]/browse}/components/pureFileTreePanel.tsx (99%) delete mode 100644 packages/web/src/features/fileTree/api.ts delete mode 100644 packages/web/src/features/fileTree/types.ts rename packages/web/src/features/{search => git}/dateUtils.test.ts (100%) rename packages/web/src/features/{search => git}/dateUtils.ts (100%) create mode 100644 packages/web/src/features/git/getFileSourceApi.ts create mode 100644 packages/web/src/features/git/getFilesApi.ts create mode 100644 packages/web/src/features/git/getFolderContentsApi.ts create mode 100644 packages/web/src/features/git/getTreeApi.ts create mode 100644 packages/web/src/features/git/index.ts rename packages/web/src/features/{search/gitApi.test.ts => git/listCommitsApi.test.ts} (99%) rename packages/web/src/features/{search/gitApi.ts => git/listCommitsApi.ts} (100%) create mode 100644 packages/web/src/features/git/types.ts rename packages/web/src/features/{fileTree => git}/utils.test.ts (99%) rename packages/web/src/features/{fileTree => git}/utils.ts (96%) delete mode 100644 packages/web/src/features/search/fileSourceApi.ts diff --git a/packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx b/packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx index 28fa54e64..cc5be9090 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx +++ b/packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx @@ -4,7 +4,7 @@ import { Separator } from "@/components/ui/separator"; import { cn, getCodeHostInfoForRepo, isServiceError } from "@/lib/utils"; import Image from "next/image"; import { PureCodePreviewPanel } from "./pureCodePreviewPanel"; -import { getFileSource } from "@/features/search/fileSourceApi"; +import { getFileSource } from '@/features/git'; interface CodePreviewPanelProps { path: string; diff --git a/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx b/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx index 269647360..1c4f6ae43 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx +++ b/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx @@ -1,12 +1,12 @@ 'use client'; import { useRef } from "react"; -import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent"; +import { FileTreeItemComponent } from "@/app/[domain]/browse/components/fileTreeItemComponent"; import { getBrowsePath } from "../../hooks/utils"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useBrowseParams } from "../../hooks/useBrowseParams"; import { useDomain } from "@/hooks/useDomain"; -import { FileTreeItem } from "@/features/fileTree/types"; +import { FileTreeItem } from "@/features/git"; interface PureTreePreviewPanelProps { items: FileTreeItem[]; diff --git a/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx b/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx index ada848eff..90afe2916 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx +++ b/packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx @@ -2,7 +2,7 @@ import { Separator } from "@/components/ui/separator"; import { getRepoInfoByName } from "@/actions"; import { PathHeader } from "@/app/[domain]/components/pathHeader"; -import { getFolderContents } from "@/features/fileTree/api"; +import { getFolderContents } from "@/features/git/getFolderContentsApi"; import { isServiceError } from "@/lib/utils"; import { PureTreePreviewPanel } from "./pureTreePreviewPanel"; diff --git a/packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx b/packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx index dd1014d10..efdded34e 100644 --- a/packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx +++ b/packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx @@ -9,10 +9,10 @@ import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@/compone import { useBrowseNavigation } from "../hooks/useBrowseNavigation"; import { useBrowseState } from "../hooks/useBrowseState"; import { useBrowseParams } from "../hooks/useBrowseParams"; -import { FileTreeItemIcon } from "@/features/fileTree/components/fileTreeItemIcon"; +import { FileTreeItemIcon } from "@/app/[domain]/browse/components/fileTreeItemIcon"; import { useLocalStorage } from "usehooks-ts"; import { Skeleton } from "@/components/ui/skeleton"; -import { FileTreeItem } from "@/features/fileTree/types"; +import { FileTreeItem } from "@/features/git"; import { getFiles } from "@/app/api/(client)/client"; const MAX_RESULTS = 100; diff --git a/packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx b/packages/web/src/app/[domain]/browse/components/fileTreeItemComponent.tsx similarity index 98% rename from packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx rename to packages/web/src/app/[domain]/browse/components/fileTreeItemComponent.tsx index aa1cb7238..42770bb08 100644 --- a/packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx +++ b/packages/web/src/app/[domain]/browse/components/fileTreeItemComponent.tsx @@ -6,7 +6,7 @@ import scrollIntoView from 'scroll-into-view-if-needed'; import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons"; import { FileTreeItemIcon } from "./fileTreeItemIcon"; import Link from "next/link"; -import { FileTreeItem } from "../types"; +import { FileTreeItem } from "@/features/git"; export const FileTreeItemComponent = ({ node, diff --git a/packages/web/src/features/fileTree/components/fileTreeItemIcon.tsx b/packages/web/src/app/[domain]/browse/components/fileTreeItemIcon.tsx similarity index 93% rename from packages/web/src/features/fileTree/components/fileTreeItemIcon.tsx rename to packages/web/src/app/[domain]/browse/components/fileTreeItemIcon.tsx index 921ae1269..cbe5fa7f7 100644 --- a/packages/web/src/features/fileTree/components/fileTreeItemIcon.tsx +++ b/packages/web/src/app/[domain]/browse/components/fileTreeItemIcon.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { VscodeFolderIcon } from "@/app/components/vscodeFolderIcon"; import { VscodeFileIcon } from "@/app/components/vscodeFileIcon"; -import { FileTreeItem } from "../types"; +import { FileTreeItem } from "@/features/git"; interface FileTreeItemIconProps { item: FileTreeItem; diff --git a/packages/web/src/features/fileTree/components/fileTreePanel.tsx b/packages/web/src/app/[domain]/browse/components/fileTreePanel.tsx similarity index 99% rename from packages/web/src/features/fileTree/components/fileTreePanel.tsx rename to packages/web/src/app/[domain]/browse/components/fileTreePanel.tsx index 89696eba4..db96aa28c 100644 --- a/packages/web/src/features/fileTree/components/fileTreePanel.tsx +++ b/packages/web/src/app/[domain]/browse/components/fileTreePanel.tsx @@ -20,7 +20,7 @@ import { GoSidebarCollapse as ExpandIcon } from "react-icons/go"; import { ImperativePanelHandle } from "react-resizable-panels"; -import { FileTreeNode } from "../types"; +import { FileTreeNode } from "@/features/git"; import { PureFileTreePanel } from "./pureFileTreePanel"; interface FileTreePanelProps { diff --git a/packages/web/src/features/fileTree/components/pureFileTreePanel.tsx b/packages/web/src/app/[domain]/browse/components/pureFileTreePanel.tsx similarity index 99% rename from packages/web/src/features/fileTree/components/pureFileTreePanel.tsx rename to packages/web/src/app/[domain]/browse/components/pureFileTreePanel.tsx index 0f7a15c1b..34a64685d 100644 --- a/packages/web/src/features/fileTree/components/pureFileTreePanel.tsx +++ b/packages/web/src/app/[domain]/browse/components/pureFileTreePanel.tsx @@ -1,6 +1,6 @@ 'use client'; -import { FileTreeNode } from "../types"; +import { FileTreeNode } from "@/features/git"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import React, { useCallback, useMemo, useRef } from "react"; import { FileTreeItemComponent } from "./fileTreeItemComponent"; diff --git a/packages/web/src/app/[domain]/browse/layout.tsx b/packages/web/src/app/[domain]/browse/layout.tsx index d7c9f1453..ce1d66a97 100644 --- a/packages/web/src/app/[domain]/browse/layout.tsx +++ b/packages/web/src/app/[domain]/browse/layout.tsx @@ -4,7 +4,7 @@ import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; import { BottomPanel } from "./components/bottomPanel"; import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle"; import { BrowseStateProvider } from "./browseStateProvider"; -import { FileTreePanel } from "@/features/fileTree/components/fileTreePanel"; +import { FileTreePanel } from "./components/fileTreePanel"; import { TopBar } from "@/app/[domain]/components/topBar"; import { useBrowseParams } from "./hooks/useBrowseParams"; import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog"; diff --git a/packages/web/src/app/api/(client)/client.ts b/packages/web/src/app/api/(client)/client.ts index 37216912a..1fa998d22 100644 --- a/packages/web/src/app/api/(client)/client.ts +++ b/packages/web/src/app/api/(client)/client.ts @@ -7,10 +7,6 @@ import { SearchRequest, SearchResponse, } from "@/features/search"; -import { - FileSourceRequest, - FileSourceResponse, -} from "@/features/search/types"; import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse, @@ -20,7 +16,9 @@ import { GetFilesResponse, GetTreeRequest, GetTreeResponse, -} from "@/features/fileTree/types"; + FileSourceRequest, + FileSourceResponse, +} from "@/features/git"; export const search = async (body: SearchRequest): Promise => { const result = await fetch("/api/search", { diff --git a/packages/web/src/app/api/(server)/commits/route.ts b/packages/web/src/app/api/(server)/commits/route.ts index 926f4b807..9e3a8173c 100644 --- a/packages/web/src/app/api/(server)/commits/route.ts +++ b/packages/web/src/app/api/(server)/commits/route.ts @@ -1,4 +1,4 @@ -import { listCommits } from "@/features/search/gitApi"; +import { listCommits } from "@/features/git"; import { buildLinkHeader } from "@/lib/pagination"; import { serviceErrorResponse, queryParamsSchemaValidationError } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; diff --git a/packages/web/src/app/api/(server)/files/route.ts b/packages/web/src/app/api/(server)/files/route.ts index 1b64efec2..afc93cdd0 100644 --- a/packages/web/src/app/api/(server)/files/route.ts +++ b/packages/web/src/app/api/(server)/files/route.ts @@ -1,7 +1,6 @@ 'use server'; -import { getFiles } from "@/features/fileTree/api"; -import { getFilesRequestSchema } from "@/features/fileTree/types"; +import { getFiles, getFilesRequestSchema } from "@/features/git/getFilesApi"; import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { NextRequest } from "next/server"; diff --git a/packages/web/src/app/api/(server)/source/route.ts b/packages/web/src/app/api/(server)/source/route.ts index c6cceb3e3..095c3dc46 100644 --- a/packages/web/src/app/api/(server)/source/route.ts +++ b/packages/web/src/app/api/(server)/source/route.ts @@ -1,6 +1,6 @@ 'use server'; -import { getFileSource } from "@/features/search/fileSourceApi"; +import { getFileSource } from '@/features/git'; import { queryParamsSchemaValidationError, serviceErrorResponse } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { NextRequest } from "next/server"; diff --git a/packages/web/src/app/api/(server)/tree/route.ts b/packages/web/src/app/api/(server)/tree/route.ts index 6f2d22530..decf72acb 100644 --- a/packages/web/src/app/api/(server)/tree/route.ts +++ b/packages/web/src/app/api/(server)/tree/route.ts @@ -1,7 +1,7 @@ 'use server'; -import { getTree } from "@/features/fileTree/api"; -import { getTreeRequestSchema } from "@/features/fileTree/types"; +import { getTree } from "@/features/git/getTreeApi"; +import { getTreeRequestSchema } from "@/features/git"; import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { NextRequest } from "next/server"; diff --git a/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts b/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts index a3fbbace9..926339aca 100644 --- a/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts +++ b/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts @@ -1,6 +1,5 @@ import { sourcebot_context, sourcebot_pr_payload } from "@/features/agents/review-agent/types"; -import { getFileSource } from "@/features/search/fileSourceApi"; -import { fileSourceResponseSchema } from "@/features/search/types"; +import { fileSourceResponseSchema, getFileSource } from '@/features/git'; import { isServiceError } from "@/lib/utils"; import { createLogger } from "@sourcebot/shared"; diff --git a/packages/web/src/features/chat/agent.ts b/packages/web/src/features/chat/agent.ts index efeb12f81..bb793b9e7 100644 --- a/packages/web/src/features/chat/agent.ts +++ b/packages/web/src/features/chat/agent.ts @@ -1,6 +1,6 @@ import { env } from "@sourcebot/shared"; import { env as clientEnv } from "@sourcebot/shared/client"; -import { getFileSource } from "@/features/search/fileSourceApi"; +import { getFileSource } from '@/features/git'; import { isServiceError } from "@/lib/utils"; import { ProviderOptions } from "@ai-sdk/provider-utils"; import { createLogger } from "@sourcebot/shared"; diff --git a/packages/web/src/features/chat/tools.ts b/packages/web/src/features/chat/tools.ts index 9594f8e3e..c932e932e 100644 --- a/packages/web/src/features/chat/tools.ts +++ b/packages/web/src/features/chat/tools.ts @@ -2,9 +2,8 @@ import { z } from "zod" import { search } from "@/features/search" import { InferToolInput, InferToolOutput, InferUITool, tool, ToolUIPart } from "ai"; import { isServiceError } from "@/lib/utils"; -import { getFileSource } from "../search/fileSourceApi"; +import { FileSourceResponse, getFileSource } from '@/features/git'; import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/api"; -import { FileSourceResponse } from "../search/types"; import { addLineNumbers, buildSearchQuery } from "./utils"; import { toolNames } from "./constants"; import { getRepos } from "@/actions"; diff --git a/packages/web/src/features/fileTree/api.ts b/packages/web/src/features/fileTree/api.ts deleted file mode 100644 index 6b5ab574f..000000000 --- a/packages/web/src/features/fileTree/api.ts +++ /dev/null @@ -1,194 +0,0 @@ -import 'server-only'; - -import { sew } from '@/actions'; -import { notFound, unexpectedError } from '@/lib/serviceError'; -import { withOptionalAuthV2 } from '@/withAuthV2'; -import { createLogger, getRepoPath } from '@sourcebot/shared'; -import { simpleGit } from 'simple-git'; -import { FileTreeItem } from './types'; -import { buildFileTree, isPathValid, normalizePath } from './utils'; -import { compareFileTreeItems } from './utils'; - -const logger = createLogger('file-tree'); - -/** - * Returns a file tree spanning the union of all provided paths for the given - * repo/revision, including intermediate directories needed to connect them - * into a single tree. - */ -export const getTree = async (params: { repoName: string, revisionName: string, paths: string[] }) => sew(() => - withOptionalAuthV2(async ({ org, prisma }) => { - const { repoName, revisionName, paths } = params; - const repo = await prisma.repo.findFirst({ - where: { - name: repoName, - orgId: org.id, - }, - }); - - if (!repo) { - return notFound(); - } - - const { path: repoPath } = getRepoPath(repo); - - const git = simpleGit().cwd(repoPath); - if (!paths.every(path => isPathValid(path))) { - return notFound(); - } - - const normalizedPaths = paths.map(path => normalizePath(path)); - - let result: string = ''; - try { - - const command = [ - // Disable quoting of non-ASCII characters in paths - '-c', 'core.quotePath=false', - 'ls-tree', - revisionName, - // format as output as {type},{path} - '--format=%(objecttype),%(path)', - // include tree nodes - '-t', - '--', - '.', - ...normalizedPaths, - ]; - - result = await git.raw(command); - } catch (error) { - logger.error('git ls-tree failed.', { error }); - return unexpectedError('git ls-tree command failed.'); - } - - const lines = result.split('\n').filter(line => line.trim()); - - const flatList = lines.map(line => { - const [type, path] = line.split(','); - return { - type, - path, - } - }); - - const tree = buildFileTree(flatList); - - return { - tree, - } - - })); - -/** - * Returns the contents of a folder at a given path in a given repository, - * at a given revision. - */ -export const getFolderContents = async (params: { repoName: string, revisionName: string, path: string }) => sew(() => - withOptionalAuthV2(async ({ org, prisma }) => { - const { repoName, revisionName, path } = params; - const repo = await prisma.repo.findFirst({ - where: { - name: repoName, - orgId: org.id, - }, - }); - - if (!repo) { - return notFound(); - } - - const { path: repoPath } = getRepoPath(repo); - const git = simpleGit().cwd(repoPath); - - if (!isPathValid(path)) { - return notFound(); - } - const normalizedPath = normalizePath(path); - - let result: string; - try { - result = await git.raw([ - // Disable quoting of non-ASCII characters in paths - '-c', 'core.quotePath=false', - 'ls-tree', - revisionName, - // format as output as {type},{path} - '--format=%(objecttype),%(path)', - ...(normalizedPath.length === 0 ? [] : [normalizedPath]), - ]); - } catch (error) { - logger.error('git ls-tree failed.', { error }); - return unexpectedError('git ls-tree command failed.'); - } - - const lines = result.split('\n').filter(line => line.trim()); - - const contents: FileTreeItem[] = lines.map(line => { - const [type, path] = line.split(','); - const name = path.split('/').pop() ?? ''; - - return { - type, - path, - name, - } - }); - - // Sort the contents in place, first by type (trees before blobs), then by name. - contents.sort(compareFileTreeItems); - - return contents; - })); - -export const getFiles = async (params: { repoName: string, revisionName: string }) => sew(() => - withOptionalAuthV2(async ({ org, prisma }) => { - const { repoName, revisionName } = params; - - const repo = await prisma.repo.findFirst({ - where: { - name: repoName, - orgId: org.id, - }, - }); - - if (!repo) { - return notFound(); - } - - const { path: repoPath } = getRepoPath(repo); - - const git = simpleGit().cwd(repoPath); - - let result: string; - try { - result = await git.raw([ - // Disable quoting of non-ASCII characters in paths - '-c', 'core.quotePath=false', - 'ls-tree', - revisionName, - // recursive - '-r', - // only return the names of the files - '--name-only', - ]); - } catch (error) { - logger.error('git ls-tree failed.', { error }); - return unexpectedError('git ls-tree command failed.'); - } - - const paths = result.split('\n').filter(line => line.trim()); - - const files: FileTreeItem[] = paths.map(path => { - const name = path.split('/').pop() ?? ''; - return { - type: 'blob', - path, - name, - } - }); - - return files; - - })); - diff --git a/packages/web/src/features/fileTree/types.ts b/packages/web/src/features/fileTree/types.ts deleted file mode 100644 index 29aeae121..000000000 --- a/packages/web/src/features/fileTree/types.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { z } from "zod"; - -export const getTreeRequestSchema = z.object({ - repoName: z.string(), - revisionName: z.string(), - paths: z.array(z.string()), -}); -export type GetTreeRequest = z.infer; - -export const getFilesRequestSchema = z.object({ - repoName: z.string(), - revisionName: z.string(), -}); -export type GetFilesRequest = z.infer; - -export const getFolderContentsRequestSchema = z.object({ - repoName: z.string(), - revisionName: z.string(), - path: z.string(), -}); -export type GetFolderContentsRequest = z.infer; - -export const fileTreeItemSchema = z.object({ - type: z.string(), - path: z.string(), - name: z.string(), -}); -export type FileTreeItem = z.infer; - -type FileTreeNodeType = { - type: string; - path: string; - name: string; - children: FileTreeNodeType[]; -}; - -export const fileTreeNodeSchema: z.ZodType = z.lazy(() => z.object({ - type: z.string(), - path: z.string(), - name: z.string(), - children: z.array(fileTreeNodeSchema), -})); -export type FileTreeNode = z.infer; - -export const getTreeResponseSchema = z.object({ - tree: fileTreeNodeSchema, -}); -export type GetTreeResponse = z.infer; - -export const getFilesResponseSchema = z.array(fileTreeItemSchema); -export type GetFilesResponse = z.infer; - -export const getFolderContentsResponseSchema = z.array(fileTreeItemSchema); -export type GetFolderContentsResponse = z.infer; - diff --git a/packages/web/src/features/search/dateUtils.test.ts b/packages/web/src/features/git/dateUtils.test.ts similarity index 100% rename from packages/web/src/features/search/dateUtils.test.ts rename to packages/web/src/features/git/dateUtils.test.ts diff --git a/packages/web/src/features/search/dateUtils.ts b/packages/web/src/features/git/dateUtils.ts similarity index 100% rename from packages/web/src/features/search/dateUtils.ts rename to packages/web/src/features/git/dateUtils.ts diff --git a/packages/web/src/features/git/getFileSourceApi.ts b/packages/web/src/features/git/getFileSourceApi.ts new file mode 100644 index 000000000..a6a87a9b7 --- /dev/null +++ b/packages/web/src/features/git/getFileSourceApi.ts @@ -0,0 +1,90 @@ +import { sew } from '@/actions'; +import { getBrowsePath } from '@/app/[domain]/browse/hooks/utils'; +import { SINGLE_TENANT_ORG_DOMAIN } from '@/lib/constants'; +import { detectLanguageFromFilename } from '@/lib/languageDetection'; +import { ServiceError, notFound, fileNotFound, unexpectedError } from '@/lib/serviceError'; +import { getCodeHostBrowseFileAtBranchUrl } from '@/lib/utils'; +import { withOptionalAuthV2 } from '@/withAuthV2'; +import { getRepoPath } from '@sourcebot/shared'; +import simpleGit from 'simple-git'; +import z from 'zod'; +import { CodeHostType } from '@sourcebot/db'; + +export const fileSourceRequestSchema = z.object({ + path: z.string(), + repo: z.string(), + ref: z.string().optional(), +}); +export type FileSourceRequest = z.infer; + +export const fileSourceResponseSchema = z.object({ + source: z.string(), + language: z.string(), + path: z.string(), + repo: z.string(), + repoCodeHostType: z.nativeEnum(CodeHostType), + repoDisplayName: z.string().optional(), + repoExternalWebUrl: z.string().optional(), + branch: z.string().optional(), + webUrl: z.string(), + externalWebUrl: z.string().optional(), +}); +export type FileSourceResponse = z.infer; + +export const getFileSource = async ({ path: filePath, repo: repoName, ref }: FileSourceRequest): Promise => sew(() => withOptionalAuthV2(async ({ org, prisma }) => { + const repo = await prisma.repo.findFirst({ + where: { name: repoName, orgId: org.id }, + }); + if (!repo) { + return notFound(`Repository "${repoName}" not found.`); + } + + const { path: repoPath } = getRepoPath(repo); + const git = simpleGit().cwd(repoPath); + + const gitRef = ref ?? + repo.defaultBranch ?? + 'HEAD'; + + let source: string; + try { + source = await git.raw(['show', `${gitRef}:${filePath}`]); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + if (errorMessage.includes('does not exist') || errorMessage.includes('fatal: path')) { + return fileNotFound(filePath, repoName); + } + if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) { + return unexpectedError(`Invalid git reference: ${gitRef}`); + } + throw error; + } + + const language = detectLanguageFromFilename(filePath); + const webUrl = getBrowsePath({ + repoName: repo.name, + revisionName: ref, + path: filePath, + pathType: 'blob', + domain: SINGLE_TENANT_ORG_DOMAIN, + }); + const externalWebUrl = getCodeHostBrowseFileAtBranchUrl({ + webUrl: repo.webUrl, + codeHostType: repo.external_codeHostType, + branchName: gitRef, + filePath, + }); + + return { + source, + language, + path: filePath, + repo: repoName, + repoCodeHostType: repo.external_codeHostType, + repoDisplayName: repo.displayName ?? undefined, + repoExternalWebUrl: repo.webUrl ?? undefined, + branch: ref, + webUrl, + externalWebUrl, + } satisfies FileSourceResponse; +})); diff --git a/packages/web/src/features/git/getFilesApi.ts b/packages/web/src/features/git/getFilesApi.ts new file mode 100644 index 000000000..7f10d480b --- /dev/null +++ b/packages/web/src/features/git/getFilesApi.ts @@ -0,0 +1,66 @@ +import { sew } from '@/actions'; +import { FileTreeItem, fileTreeItemSchema } from "./types"; +import { ServiceError, unexpectedError } from '@/lib/serviceError'; +import { withOptionalAuthV2 } from "@/withAuthV2"; +import { getRepoPath } from '@sourcebot/shared'; +import { notFound } from 'next/navigation'; +import simpleGit from 'simple-git'; +import z from 'zod'; +import { logger } from './utils'; + +export const getFilesRequestSchema = z.object({ + repoName: z.string(), + revisionName: z.string(), +}); +export type GetFilesRequest = z.infer; + +export const getFilesResponseSchema = z.array(fileTreeItemSchema); +export type GetFilesResponse = z.infer; + +export const getFiles = async ({ repoName, revisionName }: GetFilesRequest): Promise => sew(() => + withOptionalAuthV2(async ({ org, prisma }) => { + const repo = await prisma.repo.findFirst({ + where: { + name: repoName, + orgId: org.id, + }, + }); + + if (!repo) { + return notFound(); + } + + const { path: repoPath } = getRepoPath(repo); + + const git = simpleGit().cwd(repoPath); + + let result: string; + try { + result = await git.raw([ + // Disable quoting of non-ASCII characters in paths + '-c', 'core.quotePath=false', + 'ls-tree', + revisionName, + // recursive + '-r', + // only return the names of the files + '--name-only', + ]); + } catch (error) { + logger.error('git ls-tree failed.', { error }); + return unexpectedError('git ls-tree command failed.'); + } + + const paths = result.split('\n').filter(line => line.trim()); + + const files: FileTreeItem[] = paths.map(path => { + const name = path.split('/').pop() ?? ''; + return { + type: 'blob', + path, + name, + } + }); + + return files; + })); diff --git a/packages/web/src/features/git/getFolderContentsApi.ts b/packages/web/src/features/git/getFolderContentsApi.ts new file mode 100644 index 000000000..23a454362 --- /dev/null +++ b/packages/web/src/features/git/getFolderContentsApi.ts @@ -0,0 +1,78 @@ +import { sew } from '@/actions'; +import { FileTreeItem } from "./types"; +import { unexpectedError } from '@/lib/serviceError'; +import { withOptionalAuthV2 } from "@/withAuthV2"; +import { getRepoPath } from '@sourcebot/shared'; +import { notFound } from 'next/navigation'; +import simpleGit from 'simple-git'; +import z from 'zod'; +import { compareFileTreeItems, isPathValid, logger, normalizePath } from './utils'; + +export const getFolderContentsRequestSchema = z.object({ + repoName: z.string(), + revisionName: z.string(), + path: z.string(), +}); +export type GetFolderContentsRequest = z.infer; + +/** + * Returns the contents of a folder at a given path in a given repository, + * at a given revision. + */ +export const getFolderContents = async ({ repoName, revisionName, path }: GetFolderContentsRequest) => sew(() => + withOptionalAuthV2(async ({ org, prisma }) => { + const repo = await prisma.repo.findFirst({ + where: { + name: repoName, + orgId: org.id, + }, + }); + + if (!repo) { + return notFound(); + } + + const { path: repoPath } = getRepoPath(repo); + const git = simpleGit().cwd(repoPath); + + if (!isPathValid(path)) { + return notFound(); + } + const normalizedPath = normalizePath(path); + + let result: string; + try { + result = await git.raw([ + // Disable quoting of non-ASCII characters in paths + '-c', 'core.quotePath=false', + 'ls-tree', + revisionName, + // format as output as {type},{path} + '--format=%(objecttype),%(path)', + ...(normalizedPath.length === 0 ? [] : [normalizedPath]), + ]); + } catch (error) { + logger.error('git ls-tree failed.', { error }); + return unexpectedError('git ls-tree command failed.'); + } + + const lines = result.split('\n').filter(line => line.trim()); + + const contents: FileTreeItem[] = lines.map(line => { + const commaIndex = line.indexOf(','); + const type = line.substring(0, commaIndex); + const path = line.substring(commaIndex + 1); + const name = path.split('/').pop() ?? ''; + + return { + type, + path, + name, + } + }); + + // Sort the contents in place, first by type (trees before blobs), then by name. + contents.sort(compareFileTreeItems); + + return contents; + })); diff --git a/packages/web/src/features/git/getTreeApi.ts b/packages/web/src/features/git/getTreeApi.ts new file mode 100644 index 000000000..6cca62e30 --- /dev/null +++ b/packages/web/src/features/git/getTreeApi.ts @@ -0,0 +1,90 @@ +import { sew } from '@/actions'; +import { ServiceError, unexpectedError } from '@/lib/serviceError'; +import { withOptionalAuthV2 } from "@/withAuthV2"; +import { getRepoPath } from '@sourcebot/shared'; +import { notFound } from 'next/navigation'; +import simpleGit from 'simple-git'; +import z from 'zod'; +import { fileTreeNodeSchema } from './types'; +import { buildFileTree, isPathValid, logger, normalizePath } from './utils'; + +export const getTreeRequestSchema = z.object({ + repoName: z.string(), + revisionName: z.string(), + paths: z.array(z.string()), +}); +export type GetTreeRequest = z.infer; + +export const getTreeResponseSchema = z.object({ + tree: fileTreeNodeSchema, +}); +export type GetTreeResponse = z.infer; + +/** + * Returns a file tree spanning the union of all provided paths for the given + * repo/revision, including intermediate directories needed to connect them + * into a single tree. + */ +export const getTree = async ({ repoName, revisionName, paths }: GetTreeRequest): Promise => sew(() => + withOptionalAuthV2(async ({ org, prisma }) => { + const repo = await prisma.repo.findFirst({ + where: { + name: repoName, + orgId: org.id, + }, + }); + + if (!repo) { + return notFound(); + } + + const { path: repoPath } = getRepoPath(repo); + + const git = simpleGit().cwd(repoPath); + if (!paths.every(path => isPathValid(path))) { + return notFound(); + } + + const normalizedPaths = paths.map(path => normalizePath(path)); + + let result: string = ''; + try { + + const command = [ + // Disable quoting of non-ASCII characters in paths + '-c', 'core.quotePath=false', + 'ls-tree', + revisionName, + // format as output as {type},{path} + '--format=%(objecttype),%(path)', + // include tree nodes + '-t', + '--', + '.', + ...normalizedPaths, + ]; + + result = await git.raw(command); + } catch (error) { + logger.error('git ls-tree failed.', { error }); + return unexpectedError('git ls-tree command failed.'); + } + + const lines = result.split('\n').filter(line => line.trim()); + + const flatList = lines.map(line => { + const commaIndex = line.indexOf(','); + const type = line.substring(0, commaIndex); + const path = line.substring(commaIndex + 1); + return { + type, + path, + } + }); + + const tree = buildFileTree(flatList); + + return { + tree, + } + })); diff --git a/packages/web/src/features/git/index.ts b/packages/web/src/features/git/index.ts new file mode 100644 index 000000000..cfb4c39c4 --- /dev/null +++ b/packages/web/src/features/git/index.ts @@ -0,0 +1,6 @@ +export * from './getFilesApi'; +export * from './getFolderContentsApi'; +export * from './getTreeApi'; +export * from './getFileSourceApi'; +export * from './listCommitsApi'; +export * from './types'; \ No newline at end of file diff --git a/packages/web/src/features/search/gitApi.test.ts b/packages/web/src/features/git/listCommitsApi.test.ts similarity index 99% rename from packages/web/src/features/search/gitApi.test.ts rename to packages/web/src/features/git/listCommitsApi.test.ts index 38c4c6819..3cc332339 100644 --- a/packages/web/src/features/search/gitApi.test.ts +++ b/packages/web/src/features/git/listCommitsApi.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { listCommits } from './gitApi'; +import { listCommits } from './listCommitsApi'; import * as dateUtils from './dateUtils'; // Mock dependencies diff --git a/packages/web/src/features/search/gitApi.ts b/packages/web/src/features/git/listCommitsApi.ts similarity index 100% rename from packages/web/src/features/search/gitApi.ts rename to packages/web/src/features/git/listCommitsApi.ts diff --git a/packages/web/src/features/git/types.ts b/packages/web/src/features/git/types.ts new file mode 100644 index 000000000..6c8b2de80 --- /dev/null +++ b/packages/web/src/features/git/types.ts @@ -0,0 +1,24 @@ +import z from "zod"; + +export const fileTreeItemSchema = z.object({ + type: z.string(), + path: z.string(), + name: z.string(), +}); + +export type FileTreeItem = z.infer; + +type FileTreeNodeType = { + type: string; + path: string; + name: string; + children: FileTreeNodeType[]; +}; + +export const fileTreeNodeSchema: z.ZodType = z.lazy(() => z.object({ + type: z.string(), + path: z.string(), + name: z.string(), + children: z.array(fileTreeNodeSchema), +})); +export type FileTreeNode = z.infer; diff --git a/packages/web/src/features/fileTree/utils.test.ts b/packages/web/src/features/git/utils.test.ts similarity index 99% rename from packages/web/src/features/fileTree/utils.test.ts rename to packages/web/src/features/git/utils.test.ts index 4785689c8..787144efc 100644 --- a/packages/web/src/features/fileTree/utils.test.ts +++ b/packages/web/src/features/git/utils.test.ts @@ -30,6 +30,7 @@ test('isPathValid allows paths with dots', () => { expect(isPathValid('a/b/[..path]')).toBe(true); }); + test('buildFileTree handles a empty flat list', () => { const flatList: { type: string, path: string }[] = []; const tree = buildFileTree(flatList); diff --git a/packages/web/src/features/fileTree/utils.ts b/packages/web/src/features/git/utils.ts similarity index 96% rename from packages/web/src/features/fileTree/utils.ts rename to packages/web/src/features/git/utils.ts index b654a6a29..2934028ce 100644 --- a/packages/web/src/features/fileTree/utils.ts +++ b/packages/web/src/features/git/utils.ts @@ -1,5 +1,19 @@ +import { createLogger } from '@sourcebot/shared'; import { FileTreeItem, FileTreeNode } from "./types"; +export const logger = createLogger('git'); + +// @note: we don't allow directory traversal +// or null bytes in the path. +export const isPathValid = (path: string) => { + const pathSegments = path.split('/'); + if (pathSegments.some(segment => segment === '..') || path.includes('\0')) { + return false; + } + + return true; +} + export const normalizePath = (path: string): string => { // Normalize the path by... let normalizedPath = path; @@ -21,17 +35,15 @@ export const normalizePath = (path: string): string => { return normalizedPath; } -// @note: we don't allow directory traversal -// or null bytes in the path. -export const isPathValid = (path: string) => { - const pathSegments = path.split('/'); - if (pathSegments.some(segment => segment === '..') || path.includes('\0')) { - return false; +export const compareFileTreeItems = (a: FileTreeItem, b: FileTreeItem): number => { + if (a.type !== b.type) { + return a.type === 'tree' ? -1 : 1; } - - return true; + return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }); } + + export const buildFileTree = (flatList: { type: string, path: string }[]): FileTreeNode => { const root: FileTreeNode = { name: 'root', @@ -79,11 +91,4 @@ export const buildFileTree = (flatList: { type: string, path: string }[]): FileT }; return sortTree(root); -} - -export const compareFileTreeItems = (a: FileTreeItem, b: FileTreeItem): number => { - if (a.type !== b.type) { - return a.type === 'tree' ? -1 : 1; - } - return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }); -} +} \ No newline at end of file diff --git a/packages/web/src/features/search/fileSourceApi.ts b/packages/web/src/features/search/fileSourceApi.ts deleted file mode 100644 index b7735958e..000000000 --- a/packages/web/src/features/search/fileSourceApi.ts +++ /dev/null @@ -1,70 +0,0 @@ -import 'server-only'; -import { fileNotFound, notFound, ServiceError, unexpectedError } from "../../lib/serviceError"; -import { FileSourceRequest, FileSourceResponse } from "./types"; -import { sew } from "@/actions"; -import { withOptionalAuthV2 } from "@/withAuthV2"; -import { getRepoPath } from '@sourcebot/shared'; -import { simpleGit } from 'simple-git'; -import { detectLanguageFromFilename } from "@/lib/languageDetection"; -import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils"; -import { getCodeHostBrowseFileAtBranchUrl } from "@/lib/utils"; -import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"; - -export const getFileSource = async ({ path: filePath, repo: repoName, ref }: FileSourceRequest): Promise => sew(() => - withOptionalAuthV2(async ({ org, prisma }) => { - const repo = await prisma.repo.findFirst({ - where: { name: repoName, orgId: org.id }, - }); - if (!repo) { - return notFound(`Repository "${repoName}" not found.`); - } - - const { path: repoPath } = getRepoPath(repo); - const git = simpleGit().cwd(repoPath); - - const gitRef = ref ?? - repo.defaultBranch ?? - 'HEAD'; - - let source: string; - try { - source = await git.raw(['show', `${gitRef}:${filePath}`]); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('does not exist') || errorMessage.includes('fatal: path')) { - return fileNotFound(filePath, repoName); - } - if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) { - return unexpectedError(`Invalid git reference: ${gitRef}`); - } - throw error; - } - - const language = detectLanguageFromFilename(filePath); - const webUrl = getBrowsePath({ - repoName: repo.name, - revisionName: ref, - path: filePath, - pathType: 'blob', - domain: SINGLE_TENANT_ORG_DOMAIN, - }); - const externalWebUrl = getCodeHostBrowseFileAtBranchUrl({ - webUrl: repo.webUrl, - codeHostType: repo.external_codeHostType, - branchName: gitRef, - filePath, - }); - - return { - source, - language, - path: filePath, - repo: repoName, - repoCodeHostType: repo.external_codeHostType, - repoDisplayName: repo.displayName ?? undefined, - repoExternalWebUrl: repo.webUrl ?? undefined, - branch: ref, - webUrl, - externalWebUrl, - } satisfies FileSourceResponse; - })); diff --git a/packages/web/src/features/search/types.ts b/packages/web/src/features/search/types.ts index 586385fe9..9e9b9aaa4 100644 --- a/packages/web/src/features/search/types.ts +++ b/packages/web/src/features/search/types.ts @@ -145,24 +145,3 @@ export const streamedSearchResponseSchema = z.discriminatedUnion('type', [ ]); export type StreamedSearchResponse = z.infer; - -export const fileSourceRequestSchema = z.object({ - path: z.string(), - repo: z.string(), - ref: z.string().optional(), -}); -export type FileSourceRequest = z.infer; - -export const fileSourceResponseSchema = z.object({ - source: z.string(), - language: z.string(), - path: z.string(), - repo: z.string(), - repoCodeHostType: z.nativeEnum(CodeHostType), - repoDisplayName: z.string().optional(), - repoExternalWebUrl: z.string().optional(), - branch: z.string().optional(), - webUrl: z.string(), - externalWebUrl: z.string().optional(), -}); -export type FileSourceResponse = z.infer; From ff5b11a965f143c9c7b8e160c1a090d12b8090af Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 30 Jan 2026 20:59:53 -0800 Subject: [PATCH 2/5] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da91d9e16..78ec7003d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Fixed issue where files with a comma would not render correctly in file tree. [#831](https://github.com/sourcebot-dev/sourcebot/pull/831) + ### Changed - Changed `/api/source` api to support fetching source code for any revision, not just revisions that are indexed by zoekt. [#829](https://github.com/sourcebot-dev/sourcebot/pull/829) From ad108dd842909cc3b5082bdd50c9dff7a770ea07 Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Fri, 30 Jan 2026 21:22:51 -0800 Subject: [PATCH 3/5] Update packages/web/src/features/git/getFolderContentsApi.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/web/src/features/git/getFolderContentsApi.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web/src/features/git/getFolderContentsApi.ts b/packages/web/src/features/git/getFolderContentsApi.ts index 23a454362..b207b2c43 100644 --- a/packages/web/src/features/git/getFolderContentsApi.ts +++ b/packages/web/src/features/git/getFolderContentsApi.ts @@ -60,6 +60,9 @@ export const getFolderContents = async ({ repoName, revisionName, path }: GetFol const contents: FileTreeItem[] = lines.map(line => { const commaIndex = line.indexOf(','); + if (commaIndex === -1) { + throw new Error(`Unexpected ls-tree output: ${line}`); + } const type = line.substring(0, commaIndex); const path = line.substring(commaIndex + 1); const name = path.split('/').pop() ?? ''; From 5396348489f5cb6d389a3ae74c8a6ed4cfdea2cd Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Fri, 30 Jan 2026 21:23:38 -0800 Subject: [PATCH 4/5] Update packages/web/src/features/git/getTreeApi.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/web/src/features/git/getTreeApi.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web/src/features/git/getTreeApi.ts b/packages/web/src/features/git/getTreeApi.ts index 6cca62e30..2a3d63c11 100644 --- a/packages/web/src/features/git/getTreeApi.ts +++ b/packages/web/src/features/git/getTreeApi.ts @@ -74,6 +74,9 @@ export const getTree = async ({ repoName, revisionName, paths }: GetTreeRequest) const flatList = lines.map(line => { const commaIndex = line.indexOf(','); + if (commaIndex === -1) { + throw new Error(`Unexpected ls-tree output: ${line}`); + } const type = line.substring(0, commaIndex); const path = line.substring(commaIndex + 1); return { From 253928b5aeb8bf54891c02914dffe3740d18b5d0 Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 30 Jan 2026 21:25:12 -0800 Subject: [PATCH 5/5] feedback --- packages/mcp/src/schemas.ts | 1 - packages/web/src/features/git/getFileSourceApi.ts | 2 -- packages/web/src/features/git/getFilesApi.ts | 3 +-- packages/web/src/features/git/getFolderContentsApi.ts | 3 +-- packages/web/src/features/git/getTreeApi.ts | 3 +-- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/mcp/src/schemas.ts b/packages/mcp/src/schemas.ts index fd89bdfbe..edd877741 100644 --- a/packages/mcp/src/schemas.ts +++ b/packages/mcp/src/schemas.ts @@ -213,7 +213,6 @@ export const fileSourceResponseSchema = z.object({ repoCodeHostType: z.string(), repoDisplayName: z.string().optional(), repoExternalWebUrl: z.string().optional(), - branch: z.string().optional(), webUrl: z.string(), externalWebUrl: z.string().optional(), }); diff --git a/packages/web/src/features/git/getFileSourceApi.ts b/packages/web/src/features/git/getFileSourceApi.ts index a6a87a9b7..94492ddf3 100644 --- a/packages/web/src/features/git/getFileSourceApi.ts +++ b/packages/web/src/features/git/getFileSourceApi.ts @@ -25,7 +25,6 @@ export const fileSourceResponseSchema = z.object({ repoCodeHostType: z.nativeEnum(CodeHostType), repoDisplayName: z.string().optional(), repoExternalWebUrl: z.string().optional(), - branch: z.string().optional(), webUrl: z.string(), externalWebUrl: z.string().optional(), }); @@ -83,7 +82,6 @@ export const getFileSource = async ({ path: filePath, repo: repoName, ref }: Fil repoCodeHostType: repo.external_codeHostType, repoDisplayName: repo.displayName ?? undefined, repoExternalWebUrl: repo.webUrl ?? undefined, - branch: ref, webUrl, externalWebUrl, } satisfies FileSourceResponse; diff --git a/packages/web/src/features/git/getFilesApi.ts b/packages/web/src/features/git/getFilesApi.ts index 7f10d480b..344fc8b51 100644 --- a/packages/web/src/features/git/getFilesApi.ts +++ b/packages/web/src/features/git/getFilesApi.ts @@ -1,9 +1,8 @@ import { sew } from '@/actions'; import { FileTreeItem, fileTreeItemSchema } from "./types"; -import { ServiceError, unexpectedError } from '@/lib/serviceError'; +import { notFound, ServiceError, unexpectedError } from '@/lib/serviceError'; import { withOptionalAuthV2 } from "@/withAuthV2"; import { getRepoPath } from '@sourcebot/shared'; -import { notFound } from 'next/navigation'; import simpleGit from 'simple-git'; import z from 'zod'; import { logger } from './utils'; diff --git a/packages/web/src/features/git/getFolderContentsApi.ts b/packages/web/src/features/git/getFolderContentsApi.ts index b207b2c43..335751b59 100644 --- a/packages/web/src/features/git/getFolderContentsApi.ts +++ b/packages/web/src/features/git/getFolderContentsApi.ts @@ -1,9 +1,8 @@ import { sew } from '@/actions'; import { FileTreeItem } from "./types"; -import { unexpectedError } from '@/lib/serviceError'; +import { notFound, unexpectedError } from '@/lib/serviceError'; import { withOptionalAuthV2 } from "@/withAuthV2"; import { getRepoPath } from '@sourcebot/shared'; -import { notFound } from 'next/navigation'; import simpleGit from 'simple-git'; import z from 'zod'; import { compareFileTreeItems, isPathValid, logger, normalizePath } from './utils'; diff --git a/packages/web/src/features/git/getTreeApi.ts b/packages/web/src/features/git/getTreeApi.ts index 2a3d63c11..cd43a1659 100644 --- a/packages/web/src/features/git/getTreeApi.ts +++ b/packages/web/src/features/git/getTreeApi.ts @@ -1,8 +1,7 @@ import { sew } from '@/actions'; -import { ServiceError, unexpectedError } from '@/lib/serviceError'; +import { notFound, ServiceError, unexpectedError } from '@/lib/serviceError'; import { withOptionalAuthV2 } from "@/withAuthV2"; import { getRepoPath } from '@sourcebot/shared'; -import { notFound } from 'next/navigation'; import simpleGit from 'simple-git'; import z from 'zod'; import { fileTreeNodeSchema } from './types';