diff --git a/CHANGELOG.md b/CHANGELOG.md index ecadb10eb..0388c8b55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Properly map all hotkeys in UI based on the platform [#784](https://github.com/sourcebot-dev/sourcebot/pull/784) - Allow parenthesis in query and filter terms [#788](https://github.com/sourcebot-dev/sourcebot/pull/788) +- Fixed issue where Sourcebot would not index the new default branch when changed. [#789](https://github.com/sourcebot-dev/sourcebot/pull/789) + +### Changed +- Changed the UI to display the default branch name instead of HEAD where applicable. [#789](https://github.com/sourcebot-dev/sourcebot/pull/789) ## [4.10.16] - 2026-01-22 diff --git a/packages/backend/src/git.ts b/packages/backend/src/git.ts index 899770017..cc99e2837 100644 --- a/packages/backend/src/git.ts +++ b/packages/backend/src/git.ts @@ -123,6 +123,17 @@ export const fetchRepository = async ( "--prune", "--progress" ]); + + // Update HEAD to match the remote's default branch. This handles the case where the remote's + // default branch changes. + const remoteDefaultBranch = await getRemoteDefaultBranch({ + path, + cloneUrl, + }); + + if (remoteDefaultBranch) { + await git.raw(['symbolic-ref', 'HEAD', `refs/heads/${remoteDefaultBranch}`]); + } } catch (error: unknown) { const baseLog = `Failed to fetch repository: ${path}`; if (env.SOURCEBOT_LOG_LEVEL !== "debug") { @@ -297,4 +308,64 @@ export const getCommitHashForRefName = async ({ logger.debug(error); return undefined; } -} \ No newline at end of file +} + +/** + * Gets the default branch name from the remote repository by querying what + * the remote's HEAD symbolic ref points to. + * + * This is useful for detecting when a remote repository's default branch has + * changed (e.g., from "master" to "main"). + * + * @returns The branch name (e.g., "main", "master") or undefined if it cannot be determined + */ +export const getRemoteDefaultBranch = async ({ + path, + cloneUrl, +}: { + path: string, + cloneUrl: string, +}) => { + const git = createGitClientForPath(path); + try { + const remoteHead = await git.raw(['ls-remote', '--symref', cloneUrl, 'HEAD']); + const match = remoteHead.match(/^ref: refs\/heads\/(\S+)\s+HEAD/m); + if (match) { + return match[1]; + } + } catch (error: unknown) { + // Avoid printing error here since cloneUrl may contain credentials. + console.error(`Failed to get remote default branch for repository: ${path}`); + return undefined; + } +} + +/** + * Gets the branch name that the local HEAD symbolic ref points to. + * + * In a git repository, HEAD is typically a symbolic reference that points to + * a branch (e.g., refs/heads/main). This function resolves that symbolic ref + * and returns just the branch name. + * + * @returns The branch name (e.g., "main", "master") or undefined if HEAD is not a symbolic ref + */ +export const getLocalDefaultBranch = async ({ + path, +}: { + path: string, +}) => { + const git = createGitClientForPath(path); + + try { + const ref = await git.raw(['symbolic-ref', 'HEAD']); + // Returns something like "refs/heads/main\n", so trim and remove prefix + const trimmed = ref.trim(); + const match = trimmed.match(/^refs\/heads\/(.+)$/); + if (match) { + return match[1]; + } + } catch (error: unknown) { + console.error(`Failed to get local default branch for repository: ${path}`); + return undefined; + } +} diff --git a/packages/backend/src/repoIndexManager.ts b/packages/backend/src/repoIndexManager.ts index c577f39fd..e83a84d61 100644 --- a/packages/backend/src/repoIndexManager.ts +++ b/packages/backend/src/repoIndexManager.ts @@ -8,7 +8,7 @@ import { Job, Queue, ReservedJob, Worker } from "groupmq"; import { Redis } from 'ioredis'; import micromatch from 'micromatch'; import { GROUPMQ_WORKER_STOP_GRACEFUL_TIMEOUT_MS, INDEX_CACHE_DIR } from './constants.js'; -import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, getTags, isPathAValidGitRepoRoot, unsetGitConfig, upsertGitConfig } from './git.js'; +import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, getLocalDefaultBranch, getTags, isPathAValidGitRepoRoot, unsetGitConfig, upsertGitConfig } from './git.js'; import { captureEvent } from './posthog.js'; import { PromClient } from './promClient.js'; import { RepoWithConnections, Settings } from "./types.js"; @@ -364,7 +364,6 @@ export class RepoIndexManager { process.stdout.write('\n'); logger.info(`Fetched ${repo.name} (id: ${repo.id}) in ${fetchDuration_s}s`); - } else if (!isReadOnly) { logger.info(`Cloning ${repo.name} (id: ${repo.id})...`); @@ -394,9 +393,11 @@ export class RepoIndexManager { }); } - let revisions = [ - 'HEAD' - ]; + const defaultBranch = await getLocalDefaultBranch({ + path: repoPath, + }); + + let revisions = defaultBranch ? [defaultBranch] : ['HEAD']; if (metadata.branches) { const branchGlobs = metadata.branches diff --git a/packages/web/src/app/[domain]/components/pathHeader.tsx b/packages/web/src/app/[domain]/components/pathHeader.tsx index 7d373b2e7..912e18974 100644 --- a/packages/web/src/app/[domain]/components/pathHeader.tsx +++ b/packages/web/src/app/[domain]/components/pathHeader.tsx @@ -31,6 +31,7 @@ interface FileHeaderProps { displayName?: string; webUrl?: string; }, + isBranchDisplayNameVisible?: boolean; branchDisplayName?: string; branchDisplayTitle?: string; isCodeHostIconVisible?: boolean; @@ -53,6 +54,7 @@ export const PathHeader = ({ path, pathHighlightRange, branchDisplayName, + isBranchDisplayNameVisible = !!branchDisplayName, branchDisplayTitle, pathType = 'blob', isCodeHostIconVisible = true, @@ -224,7 +226,7 @@ export const PathHeader = ({ > {info?.displayName} - {branchDisplayName && ( + {(isBranchDisplayNameVisible && branchDisplayName) && (

{ - return files.some((file) => { - return file.branches?.some((branch) => branch !== 'HEAD') ?? false; - }); - }, [files]); + return searchQuery.includes('rev:'); + }, [searchQuery]); useEffect(() => { if (isStreaming || !stats) { 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 2779b301c..504352486 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx @@ -70,12 +70,12 @@ export const FileMatchContainer = ({ }, [file.branches]); const branchDisplayName = useMemo(() => { - if (!isBranchFilteringEnabled || branches.length === 0) { + if (branches.length === 0) { return undefined; } return `${branches[0]}${branches.length > 1 ? ` +${branches.length - 1}` : ''}`; - }, [branches, isBranchFilteringEnabled]); + }, [branches]); const repo = useMemo(() => { return repoInfo[file.repositoryId]; @@ -99,6 +99,7 @@ export const FileMatchContainer = ({ }} path={file.fileName.text} pathHighlightRange={fileNameRange} + isBranchDisplayNameVisible={isBranchFilteringEnabled} branchDisplayName={branchDisplayName} branchDisplayTitle={branches.join(", ")} />