Update git clone to use new repo cache (#272276)

This commit is contained in:
Alex Ross
2025-10-22 21:06:20 +02:00
committed by GitHub
parent f13b84cf80
commit dda6e09f62
6 changed files with 324 additions and 194 deletions

View File

@@ -4,9 +4,21 @@
*--------------------------------------------------------------------------------------------*/
import { LogOutputChannel, Memento, workspace } from 'vscode';
import * as path from 'path';
import { LRUCache } from './cache';
import { Remote } from './api/git';
import { isDescendant } from './util';
export interface RepositoryCacheInfo {
workspacePath: string; // path of the workspace folder or workspace file
}
function isRepositoryCacheInfo(obj: unknown): obj is RepositoryCacheInfo {
if (!obj || typeof obj !== 'object') {
return false;
}
const rec = obj as Record<string, unknown>;
return typeof rec.workspacePath === 'string';
}
export class RepositoryCache {
@@ -14,8 +26,8 @@ export class RepositoryCache {
private static readonly MAX_REPO_ENTRIES = 30; // Max repositories tracked
private static readonly MAX_FOLDER_ENTRIES = 10; // Max folders per repository
// Outer LRU: repoUrl -> inner LRU (folderPathOrWorkspaceFile -> true). Only keys matter.
private readonly lru = new LRUCache<string, LRUCache<string, true>>(RepositoryCache.MAX_REPO_ENTRIES);
// Outer LRU: repoUrl -> inner LRU (folderPathOrWorkspaceFile -> RepositoryCacheInfo).
private readonly lru = new LRUCache<string, LRUCache<string, RepositoryCacheInfo>>(RepositoryCache.MAX_REPO_ENTRIES);
constructor(public readonly _globalState: Memento, private readonly _logger: LogOutputChannel) {
this.load();
@@ -40,14 +52,16 @@ export class RepositoryCache {
set(repoUrl: string, rootPath: string): void {
let foldersLru = this.lru.get(repoUrl);
if (!foldersLru) {
foldersLru = new LRUCache<string, true>(RepositoryCache.MAX_FOLDER_ENTRIES);
foldersLru = new LRUCache<string, RepositoryCacheInfo>(RepositoryCache.MAX_FOLDER_ENTRIES);
}
const folderPathOrWorkspaceFile: string | undefined = this._findWorkspaceForRepo(rootPath);
if (!folderPathOrWorkspaceFile) {
return;
}
foldersLru.set(folderPathOrWorkspaceFile, true); // touch entry
foldersLru.set(folderPathOrWorkspaceFile, {
workspacePath: folderPathOrWorkspaceFile
}); // touch entry
this.lru.set(repoUrl, foldersLru);
this.save();
}
@@ -62,13 +76,7 @@ export class RepositoryCache {
const sorted = [...this._workspaceFolders].sort((a, b) => b.uri.fsPath.length - a.uri.fsPath.length);
for (const folder of sorted) {
const folderPath = folder.uri.fsPath;
const relToFolder = path.relative(folderPath, rootPath);
if (relToFolder === '' || (!relToFolder.startsWith('..') && !path.isAbsolute(relToFolder))) {
folderPathOrWorkspaceFile = folderPath;
break;
}
const relFromFolder = path.relative(rootPath, folderPath);
if (relFromFolder === '' || (!relFromFolder.startsWith('..') && !path.isAbsolute(relFromFolder))) {
if (isDescendant(folderPath, rootPath) || isDescendant(rootPath, folderPath)) {
folderPathOrWorkspaceFile = folderPath;
break;
}
@@ -105,9 +113,9 @@ export class RepositoryCache {
/**
* We should possibly support converting between ssh remotes and http remotes.
*/
get(repoUrl: string): string[] | undefined {
get(repoUrl: string): RepositoryCacheInfo[] | undefined {
const inner = this.lru.get(repoUrl);
return inner ? Array.from(inner.keys()) : undefined;
return inner ? Array.from(inner.values()) : undefined;
}
delete(repoUrl: string, folderPathOrWorkspaceFile: string) {
@@ -129,42 +137,43 @@ export class RepositoryCache {
private load(): void {
try {
const raw = this._globalState.get<[string, [string, true][]][]>(RepositoryCache.STORAGE_KEY);
if (Array.isArray(raw)) {
for (const [repo, storedFolders] of raw) {
if (typeof repo !== 'string' || !Array.isArray(storedFolders)) {
const raw = this._globalState.get<[string, [string, RepositoryCacheInfo][]][]>(RepositoryCache.STORAGE_KEY);
if (!Array.isArray(raw)) {
return;
}
for (const [repo, storedFolders] of raw) {
if (typeof repo !== 'string' || !Array.isArray(storedFolders)) {
continue;
}
const inner = new LRUCache<string, RepositoryCacheInfo>(RepositoryCache.MAX_FOLDER_ENTRIES);
for (const entry of storedFolders) {
if (!Array.isArray(entry) || entry.length !== 2) {
continue;
}
const inner = new LRUCache<string, true>(RepositoryCache.MAX_FOLDER_ENTRIES);
for (const entry of storedFolders) {
let folderPath: string | undefined;
if (Array.isArray(entry) && entry.length === 2) {
const [workspaceFolder, _] = entry;
if (typeof workspaceFolder === 'string') {
folderPath = workspaceFolder;
}
}
if (folderPath) {
inner.set(folderPath, true);
}
}
if (inner.size) {
this.lru.set(repo, inner);
const [folderPath, info] = entry;
if (typeof folderPath !== 'string' || !isRepositoryCacheInfo(info)) {
continue;
}
inner.set(folderPath, info);
}
if (inner.size) {
this.lru.set(repo, inner);
}
}
} catch {
this._logger.warn('[CachedRepositories][load] Failed to load cached repositories from global state.');
}
}
private save(): void {
// Serialize as [repoUrl, [folderPathOrWorkspaceFile, true][]] preserving outer LRU order.
const serialized: [string, [string, true][]][] = [];
// Serialize as [repoUrl, [folderPathOrWorkspaceFile, RepositoryCacheInfo][]] preserving outer LRU order.
const serialized: [string, [string, RepositoryCacheInfo][]][] = [];
for (const [repo, inner] of this.lru) {
const folders: [string, true][] = [];
for (const [folder, _] of inner) {
folders.push([folder, true]);
const folders: [string, RepositoryCacheInfo][] = [];
for (const [folder, info] of inner) {
folders.push([folder, info]);
}
serialized.push([repo, folders]);
}