Skip to content

Commit d03b6f0

Browse files
fix(web): Fixed issue where Sourcebot would not index the new default branch when changed (#789)
1 parent 507fc8b commit d03b6f0

File tree

6 files changed

+90
-13
lines changed

6 files changed

+90
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Fixed
1111
- Properly map all hotkeys in UI based on the platform [#784](https://github.com/sourcebot-dev/sourcebot/pull/784)
1212
- Allow parenthesis in query and filter terms [#788](https://github.com/sourcebot-dev/sourcebot/pull/788)
13+
- Fixed issue where Sourcebot would not index the new default branch when changed. [#789](https://github.com/sourcebot-dev/sourcebot/pull/789)
14+
15+
### Changed
16+
- Changed the UI to display the default branch name instead of HEAD where applicable. [#789](https://github.com/sourcebot-dev/sourcebot/pull/789)
1317

1418
## [4.10.16] - 2026-01-22
1519

packages/backend/src/git.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ export const fetchRepository = async (
123123
"--prune",
124124
"--progress"
125125
]);
126+
127+
// Update HEAD to match the remote's default branch. This handles the case where the remote's
128+
// default branch changes.
129+
const remoteDefaultBranch = await getRemoteDefaultBranch({
130+
path,
131+
cloneUrl,
132+
});
133+
134+
if (remoteDefaultBranch) {
135+
await git.raw(['symbolic-ref', 'HEAD', `refs/heads/${remoteDefaultBranch}`]);
136+
}
126137
} catch (error: unknown) {
127138
const baseLog = `Failed to fetch repository: ${path}`;
128139
if (env.SOURCEBOT_LOG_LEVEL !== "debug") {
@@ -297,4 +308,64 @@ export const getCommitHashForRefName = async ({
297308
logger.debug(error);
298309
return undefined;
299310
}
300-
}
311+
}
312+
313+
/**
314+
* Gets the default branch name from the remote repository by querying what
315+
* the remote's HEAD symbolic ref points to.
316+
*
317+
* This is useful for detecting when a remote repository's default branch has
318+
* changed (e.g., from "master" to "main").
319+
*
320+
* @returns The branch name (e.g., "main", "master") or undefined if it cannot be determined
321+
*/
322+
export const getRemoteDefaultBranch = async ({
323+
path,
324+
cloneUrl,
325+
}: {
326+
path: string,
327+
cloneUrl: string,
328+
}) => {
329+
const git = createGitClientForPath(path);
330+
try {
331+
const remoteHead = await git.raw(['ls-remote', '--symref', cloneUrl, 'HEAD']);
332+
const match = remoteHead.match(/^ref: refs\/heads\/(\S+)\s+HEAD/m);
333+
if (match) {
334+
return match[1];
335+
}
336+
} catch (error: unknown) {
337+
// Avoid printing error here since cloneUrl may contain credentials.
338+
console.error(`Failed to get remote default branch for repository: ${path}`);
339+
return undefined;
340+
}
341+
}
342+
343+
/**
344+
* Gets the branch name that the local HEAD symbolic ref points to.
345+
*
346+
* In a git repository, HEAD is typically a symbolic reference that points to
347+
* a branch (e.g., refs/heads/main). This function resolves that symbolic ref
348+
* and returns just the branch name.
349+
*
350+
* @returns The branch name (e.g., "main", "master") or undefined if HEAD is not a symbolic ref
351+
*/
352+
export const getLocalDefaultBranch = async ({
353+
path,
354+
}: {
355+
path: string,
356+
}) => {
357+
const git = createGitClientForPath(path);
358+
359+
try {
360+
const ref = await git.raw(['symbolic-ref', 'HEAD']);
361+
// Returns something like "refs/heads/main\n", so trim and remove prefix
362+
const trimmed = ref.trim();
363+
const match = trimmed.match(/^refs\/heads\/(.+)$/);
364+
if (match) {
365+
return match[1];
366+
}
367+
} catch (error: unknown) {
368+
console.error(`Failed to get local default branch for repository: ${path}`);
369+
return undefined;
370+
}
371+
}

packages/backend/src/repoIndexManager.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Job, Queue, ReservedJob, Worker } from "groupmq";
88
import { Redis } from 'ioredis';
99
import micromatch from 'micromatch';
1010
import { GROUPMQ_WORKER_STOP_GRACEFUL_TIMEOUT_MS, INDEX_CACHE_DIR } from './constants.js';
11-
import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, getTags, isPathAValidGitRepoRoot, unsetGitConfig, upsertGitConfig } from './git.js';
11+
import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, getLocalDefaultBranch, getTags, isPathAValidGitRepoRoot, unsetGitConfig, upsertGitConfig } from './git.js';
1212
import { captureEvent } from './posthog.js';
1313
import { PromClient } from './promClient.js';
1414
import { RepoWithConnections, Settings } from "./types.js";
@@ -364,7 +364,6 @@ export class RepoIndexManager {
364364

365365
process.stdout.write('\n');
366366
logger.info(`Fetched ${repo.name} (id: ${repo.id}) in ${fetchDuration_s}s`);
367-
368367
} else if (!isReadOnly) {
369368
logger.info(`Cloning ${repo.name} (id: ${repo.id})...`);
370369

@@ -394,9 +393,11 @@ export class RepoIndexManager {
394393
});
395394
}
396395

397-
let revisions = [
398-
'HEAD'
399-
];
396+
const defaultBranch = await getLocalDefaultBranch({
397+
path: repoPath,
398+
});
399+
400+
let revisions = defaultBranch ? [defaultBranch] : ['HEAD'];
400401

401402
if (metadata.branches) {
402403
const branchGlobs = metadata.branches

packages/web/src/app/[domain]/components/pathHeader.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface FileHeaderProps {
3131
displayName?: string;
3232
webUrl?: string;
3333
},
34+
isBranchDisplayNameVisible?: boolean;
3435
branchDisplayName?: string;
3536
branchDisplayTitle?: string;
3637
isCodeHostIconVisible?: boolean;
@@ -53,6 +54,7 @@ export const PathHeader = ({
5354
path,
5455
pathHighlightRange,
5556
branchDisplayName,
57+
isBranchDisplayNameVisible = !!branchDisplayName,
5658
branchDisplayTitle,
5759
pathType = 'blob',
5860
isCodeHostIconVisible = true,
@@ -224,7 +226,7 @@ export const PathHeader = ({
224226
>
225227
{info?.displayName}
226228
</Link>
227-
{branchDisplayName && (
229+
{(isBranchDisplayNameVisible && branchDisplayName) && (
228230
<p
229231
className="text-xs font-semibold text-gray-500 dark:text-gray-400 mt-[3px] flex items-center gap-0.5"
230232
title={branchDisplayTitle}

packages/web/src/app/[domain]/search/components/searchResultsPage.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,8 @@ export const SearchResultsPage = ({
104104

105105
// Look for any files that are not on the default branch.
106106
const isBranchFilteringEnabled = useMemo(() => {
107-
return files.some((file) => {
108-
return file.branches?.some((branch) => branch !== 'HEAD') ?? false;
109-
});
110-
}, [files]);
107+
return searchQuery.includes('rev:');
108+
}, [searchQuery]);
111109

112110
useEffect(() => {
113111
if (isStreaming || !stats) {

packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ export const FileMatchContainer = ({
7070
}, [file.branches]);
7171

7272
const branchDisplayName = useMemo(() => {
73-
if (!isBranchFilteringEnabled || branches.length === 0) {
73+
if (branches.length === 0) {
7474
return undefined;
7575
}
7676

7777
return `${branches[0]}${branches.length > 1 ? ` +${branches.length - 1}` : ''}`;
78-
}, [branches, isBranchFilteringEnabled]);
78+
}, [branches]);
7979

8080
const repo = useMemo(() => {
8181
return repoInfo[file.repositoryId];
@@ -99,6 +99,7 @@ export const FileMatchContainer = ({
9999
}}
100100
path={file.fileName.text}
101101
pathHighlightRange={fileNameRange}
102+
isBranchDisplayNameVisible={isBranchFilteringEnabled}
102103
branchDisplayName={branchDisplayName}
103104
branchDisplayTitle={branches.join(", ")}
104105
/>

0 commit comments

Comments
 (0)