Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Reduced the log verbosity of the worker by changing various log messages from info to debug. [#1179](https://github.com/sourcebot-dev/sourcebot/pull/1179)
- [EE] Switched symbol hover detection to use Lezer highlight tags, broadening identifier coverage. [#1194](https://github.com/sourcebot-dev/sourcebot/pull/1194)
- Improved git history and blame performance on large repositories. [#1198](https://github.com/sourcebot-dev/sourcebot/pull/1198)

## [4.17.1] - 2026-05-04

Expand Down
33 changes: 23 additions & 10 deletions packages/backend/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,6 @@ export const cloneRepository = async (
keys: ["remote.origin.url"],
signal,
});

// @note: operations that need to iterate over a lot of commits (e.g., rev-list --count)
// can be slow on larger repositories. Commit graphs are a acceleration structure that
// speed up these operations.
// @see: https://git-scm.com/docs/commit-graph
await writeCommitGraph({ path, signal });
} catch (error: unknown) {
const baseLog = `Failed to clone repository: ${path}`;

Expand Down Expand Up @@ -151,9 +145,6 @@ export const fetchRepository = async (
"+refs/heads/*:refs/heads/*",
"--prune",
"--progress",
// On fetch, ensure the commit graph is up to date.
// @see: https://git-scm.com/docs/commit-graph
"--write-commit-graph"
]);

// Update HEAD to match the remote's default branch. This handles the case where the remote's
Expand Down Expand Up @@ -490,20 +481,42 @@ export const isRepoEmpty = async ({
* Writes or updates the commit-graph file for the repository.
* This pre-computes commit metadata to speed up operations like
* rev-list --count, log, and merge-base.
*
* Also writes changed-path Bloom filters (--changed-paths), which let git
* quickly skip commits that didn't touch a given path. This accelerates
* `git log -- <path>` dramatically and `git blame` modestly on large repos.
*
* For incremental writes (the default), Bloom filters are only computed for
* commits being added in this write. Repos that already have a Bloom-less
* commit-graph from a prior version need a one-time `forceBackfill: true`
* write to backfill filters for their historical commits.
*
* @see: https://git-scm.com/docs/commit-graph
*/
export const writeCommitGraph = async ({
path,
forceBackfill,
onProgress,
signal,
}: {
path: string,
forceBackfill?: boolean,
onProgress?: onProgressFn,
signal?: AbortSignal,
}): Promise<void> => {
const git = createGitClientForPath(path, onProgress, signal);

try {
await git.raw(['commit-graph', 'write', '--reachable']);
const args = ['commit-graph', 'write', '--reachable', '--changed-paths'];
if (forceBackfill) {
// --split=replace consolidates any existing layers into a single new layer,
// which forces git to recompute Bloom filters for every commit (not just commits
// added since the last write). Used for the one-time migration of repos that have
// a Bloom-less commit-graph from before --changed-paths was enabled.
// @see: https://git-scm.com/docs/git-commit-graph#Documentation/git-commit-graph.txt-write
args.push('--split=replace');
}
await git.raw(args);
} catch (error) {
// Don't throw an exception here since this is just a performance optimization.
logger.debug(`Failed to write commit-graph for ${path}:`, error);
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/repoIndexManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ vi.mock('./git.js', () => ({
isRepoEmpty: vi.fn().mockResolvedValue(false),
unsetGitConfig: vi.fn(),
upsertGitConfig: vi.fn(),
writeCommitGraph: vi.fn(),
}));

vi.mock('./zoekt.js', () => ({
Expand Down Expand Up @@ -178,6 +179,8 @@ const createMockSettings = (): Settings => ({
enablePublicAccess: false,
experiment_repoDrivenPermissionSyncIntervalMs: 1000 * 60 * 60 * 24,
experiment_userDrivenPermissionSyncIntervalMs: 1000 * 60 * 60 * 24,
repoDrivenPermissionSyncIntervalMs: 1000 * 60 * 60 * 24,
userDrivenPermissionSyncIntervalMs: 1000 * 60 * 60 * 24,
maxAccountPermissionSyncJobConcurrency: 8,
maxRepoPermissionSyncJobConcurrency: 8,
});
Expand Down
36 changes: 35 additions & 1 deletion packages/backend/src/repoIndexManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Redis } from 'ioredis';
import micromatch from 'micromatch';
import Redlock, { ExecutionError } from 'redlock';
import { INDEX_CACHE_DIR, REPOS_CACHE_DIR, WORKER_STOP_GRACEFUL_TIMEOUT_MS } from './constants.js';
import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, getLatestCommitTimestamp, getLocalDefaultBranch, getTags, isPathAValidGitRepoRoot, isRepoEmpty, unsetGitConfig, upsertGitConfig } from './git.js';
import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, getLatestCommitTimestamp, getLocalDefaultBranch, getTags, isPathAValidGitRepoRoot, isRepoEmpty, unsetGitConfig, upsertGitConfig, writeCommitGraph } from './git.js';
import { captureEvent } from './posthog.js';
import { PromClient } from './promClient.js';
import { RepoWithConnections, Settings } from "./types.js";
Expand Down Expand Up @@ -396,6 +396,19 @@ export class RepoIndexManager {
const fetchDuration_s = durationMs / 1000;

logger.debug(`Fetched ${repo.name} (id: ${repo.id}) in ${fetchDuration_s}s`);

// Update the commit-graph after fetch. Force a full backfill the first time we
// see this repo after the --changed-paths rollout, so historical commits get
// Bloom filters. Subsequent fetches do a cheap incremental write.
const needsBackfill = !metadata.commitGraphChangedPathsBackfilledAt;
if (needsBackfill) {
logger.debug(`Backfilling changed-path Bloom filters for ${repo.name} (id: ${repo.id})...`);
}
await writeCommitGraph({
path: repoPath,
forceBackfill: needsBackfill,
signal,
});
} else if (!isReadOnly) {
logger.debug(`Cloning ${repo.name} (id: ${repo.id})...`);

Expand All @@ -411,6 +424,27 @@ export class RepoIndexManager {
const cloneDuration_s = durationMs / 1000;

logger.debug(`Cloned ${repo.name} (id: ${repo.id}) in ${cloneDuration_s}s`);

// Write the commit-graph for the freshly cloned repo.
await writeCommitGraph({
path: repoPath,
signal,
});
}

// Record that this repo's commit-graph now includes changed-path Bloom filters
// for its full history (either freshly written during clone, or backfilled above
// during fetch).
if (!isReadOnly && !metadata.commitGraphChangedPathsBackfilledAt) {
await this.db.repo.update({
where: { id: repo.id },
data: {
metadata: {
...metadata,
commitGraphChangedPathsBackfilledAt: new Date().toISOString(),
} satisfies RepoMetadata,
},
});
}

// Regardless of clone or fetch, always upsert the git config for the repo.
Expand Down
8 changes: 8 additions & 0 deletions packages/shared/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ export const repoMetadataSchema = z.object({
*/
indexedRevisions: z.array(z.string()).optional(),

/**
* Timestamp of when changed-path Bloom filters were written into the
* commit-graph for this repo's full history. Undefined means the one-time
* backfill has not yet run, so historical commits still lack Bloom filters.
* @see writeCommitGraph in packages/backend/src/git.ts
*/
commitGraphChangedPathsBackfilledAt: z.string().datetime().optional(),

/**
* Code host specific metadata, keyed by code host type.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const CodePreview = ({

return (
<div className="flex flex-col h-full">
<div className="flex flex-row bg-accent items-center justify-between pr-3 py-0.5 mt-7">
<div className="flex flex-row bg-accent items-center justify-between pr-3 py-[3px] mt-7">

{/* Gutter icon */}
<div className="flex flex-row">
Expand Down
Loading