Skip to content

Commit b695c6e

Browse files
committed
Improves branch isolation in the vector store.
Moves branch-related logic into the QdrantVectorStore class for better encapsulation and dynamic updates. Introduces a new branch name sanitization method and automatically updates the collection when branches change, enabling more reliable handling of branch-specific vector data. Current branch management now occurs directly inside the VectorStore, simplifying the architecture and improving maintainability.
1 parent f8c863d commit b695c6e

File tree

3 files changed

+71
-39
lines changed

3 files changed

+71
-39
lines changed

src/services/code-index/service-factory.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { TelemetryService } from "@roo-code/telemetry"
1818
import { TelemetryEventName } from "@roo-code/types"
1919
import { Package } from "../../shared/package"
2020
import { BATCH_SEGMENT_THRESHOLD } from "./constants"
21-
import { getCurrentBranch } from "../../utils/git"
2221

2322
/**
2423
* Factory class responsible for creating and configuring code indexing service dependencies.
@@ -28,7 +27,7 @@ export class CodeIndexServiceFactory {
2827
private readonly configManager: CodeIndexConfigManager,
2928
private readonly workspacePath: string,
3029
private readonly cacheManager: CacheManager,
31-
) { }
30+
) {}
3231

3332
/**
3433
* Creates an embedder instance based on the current configuration.
@@ -146,19 +145,12 @@ export class CodeIndexServiceFactory {
146145
throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing"))
147146
}
148147

149-
// Get current branch if branch isolation is enabled
150-
let currentBranch: string | undefined
151-
if (config.branchIsolationEnabled) {
152-
currentBranch = await getCurrentBranch(this.workspacePath)
153-
}
154-
155148
return new QdrantVectorStore(
156149
this.workspacePath,
157150
config.qdrantUrl,
158151
vectorSize,
159152
config.qdrantApiKey,
160153
config.branchIsolationEnabled,
161-
currentBranch,
162154
)
163155
}
164156

src/services/code-index/vector-store/qdrant-client.ts

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { IVectorStore } from "../interfaces/vector-store"
66
import { Payload, VectorStoreSearchResult } from "../interfaces"
77
import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE } from "../constants"
88
import { t } from "../../../i18n"
9+
import { getCurrentBranch, sanitizeBranchName } from "../../../utils/git"
910

1011
/**
1112
* Qdrant implementation of the vector store interface
@@ -15,9 +16,11 @@ export class QdrantVectorStore implements IVectorStore {
1516
private readonly DISTANCE_METRIC = "Cosine"
1617

1718
private client: QdrantClient
18-
private readonly collectionName: string
19+
private collectionName: string
1920
private readonly qdrantUrl: string = "http://localhost:6333"
2021
private readonly workspacePath: string
22+
private readonly branchIsolationEnabled: boolean
23+
private currentBranch: string | null = null
2124

2225
// Lazy collection creation flag
2326
private _collectionEnsured = false
@@ -29,23 +32,22 @@ export class QdrantVectorStore implements IVectorStore {
2932
* @param url Optional URL to the Qdrant server
3033
* @param vectorSize Size of the embedding vectors
3134
* @param apiKey Optional API key for Qdrant authentication
32-
* @param branchIsolationEnabled Whether to include branch name in collection naming
33-
* @param currentBranch Current Git branch name (used when branchIsolationEnabled is true)
35+
* @param branchIsolationEnabled Whether to use branch-specific collections
3436
*/
3537
constructor(
3638
workspacePath: string,
3739
url: string,
3840
vectorSize: number,
3941
apiKey?: string,
40-
branchIsolationEnabled?: boolean,
41-
currentBranch?: string,
42+
branchIsolationEnabled: boolean = false,
4243
) {
4344
// Parse the URL to determine the appropriate QdrantClient configuration
4445
const parsedUrl = this.parseQdrantUrl(url)
4546

4647
// Store the resolved URL for our property
4748
this.qdrantUrl = parsedUrl
4849
this.workspacePath = workspacePath
50+
this.branchIsolationEnabled = branchIsolationEnabled
4951

5052
try {
5153
const urlObj = new URL(parsedUrl)
@@ -92,34 +94,12 @@ export class QdrantVectorStore implements IVectorStore {
9294
})
9395
}
9496

95-
// Generate collection name from workspace path
97+
// Generate base collection name from workspace path
9698
const hash = createHash("sha256").update(workspacePath).digest("hex")
9799
this.vectorSize = vectorSize
98100

99-
// If branch isolation is enabled and we have a branch name, include it in the collection name
100-
if (branchIsolationEnabled && currentBranch) {
101-
const sanitizedBranch = this.sanitizeBranchName(currentBranch)
102-
this.collectionName = `ws-${hash.substring(0, 16)}-br-${sanitizedBranch}`
103-
} else {
104-
this.collectionName = `ws-${hash.substring(0, 16)}`
105-
}
106-
}
107-
108-
/**
109-
* Sanitizes a Git branch name for use in Qdrant collection naming
110-
* @param branch The branch name to sanitize
111-
* @returns A sanitized branch name safe for collection naming
112-
*/
113-
private sanitizeBranchName(branch: string): string {
114-
// Replace invalid characters with hyphens, collapse multiple hyphens, limit length
115-
return (
116-
branch
117-
.replace(/[^a-zA-Z0-9_-]/g, "-") // Replace invalid chars with hyphens
118-
.replace(/--+/g, "-") // Collapse multiple hyphens
119-
.replace(/^-+|-+$/g, "") // Remove leading/trailing hyphens
120-
.substring(0, 50) // Limit length to 50 characters
121-
.toLowerCase() || "default"
122-
) // Fallback to 'default' if empty after sanitization
101+
// Base collection name (will be updated dynamically if branch isolation is enabled)
102+
this.collectionName = `ws-${hash.substring(0, 16)}`
123103
}
124104

125105
/**
@@ -267,6 +247,11 @@ export class QdrantVectorStore implements IVectorStore {
267247
* @throws {Error} If vector dimension mismatch cannot be resolved
268248
*/
269249
async initialize(): Promise<boolean> {
250+
// Update collection name based on current branch if branch isolation is enabled
251+
if (this.branchIsolationEnabled) {
252+
await this.updateCollectionNameForBranch()
253+
}
254+
270255
try {
271256
// Use shared helper to create or validate collection
272257
const created = await this._createOrValidateCollection()
@@ -418,6 +403,11 @@ export class QdrantVectorStore implements IVectorStore {
418403
// Create and store the ensure promise
419404
this._ensurePromise = (async () => {
420405
try {
406+
// Update collection name based on current branch if branch isolation is enabled
407+
if (this.branchIsolationEnabled) {
408+
await this.updateCollectionNameForBranch()
409+
}
410+
421411
// Use shared helper to create or validate collection
422412
await this._createOrValidateCollection()
423413

@@ -701,4 +691,37 @@ export class QdrantVectorStore implements IVectorStore {
701691
const collectionInfo = await this.getCollectionInfo()
702692
return collectionInfo !== null
703693
}
694+
695+
/**
696+
* Updates the collection name based on the current Git branch
697+
* Only called when branch isolation is enabled
698+
*/
699+
private async updateCollectionNameForBranch(): Promise<void> {
700+
const branch = await getCurrentBranch(this.workspacePath)
701+
702+
// Generate base collection name
703+
const hash = createHash("sha256").update(this.workspacePath).digest("hex")
704+
let collectionName = `ws-${hash.substring(0, 16)}`
705+
706+
if (branch) {
707+
// Sanitize branch name for use in collection name
708+
const sanitizedBranch = sanitizeBranchName(branch)
709+
collectionName = `${collectionName}-br-${sanitizedBranch}`
710+
this.currentBranch = branch
711+
} else {
712+
// Detached HEAD or not a git repo - use workspace-only collection
713+
this.currentBranch = null
714+
}
715+
716+
// Update the collection name
717+
this.collectionName = collectionName
718+
}
719+
720+
/**
721+
* Gets the current branch being used for the collection
722+
* @returns The current branch name or null if not using branch isolation
723+
*/
724+
public getCurrentBranch(): string | null {
725+
return this.branchIsolationEnabled ? this.currentBranch : null
726+
}
704727
}

src/utils/git.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,23 @@ export async function getCurrentBranch(workspaceRoot: string): Promise<string |
106106
}
107107
}
108108

109+
/**
110+
* Sanitizes a Git branch name for use in collection naming or file paths
111+
* @param branch The branch name to sanitize
112+
* @returns A sanitized branch name safe for use in identifiers
113+
*/
114+
export function sanitizeBranchName(branch: string): string {
115+
// Replace invalid characters with hyphens, collapse multiple hyphens, limit length
116+
return (
117+
branch
118+
.replace(/[^a-zA-Z0-9_-]/g, "-") // Replace invalid chars with hyphens
119+
.replace(/--+/g, "-") // Collapse multiple hyphens
120+
.replace(/^-+|-+$/g, "") // Remove leading/trailing hyphens
121+
.substring(0, 50) // Limit length to 50 characters
122+
.toLowerCase() || "default"
123+
) // Fallback to 'default' if empty after sanitization
124+
}
125+
109126
/**
110127
* Converts a git URL to HTTPS format
111128
* @param url The git URL to convert

0 commit comments

Comments
 (0)