diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 91d4cbc16d5..4c865974639 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -7,7 +7,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions, CommitShortStat, DiffChange, Worktree, RepositoryKind } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions, CommitShortStat, DiffChange, Worktree, RepositoryKind, RepositoryAccessDetails } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; import { combinedDisposable, filterEvent, mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -400,6 +400,10 @@ export class ApiImpl implements API { return this.#model.repositories.map(r => new ApiRepository(r)); } + get recentRepositories(): Iterable { + return this.#model.repositoryCache.recentRepositories; + } + toGitUri(uri: Uri, ref: string): Uri { return toGitUri(uri, ref); } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index d2ff80938e0..b43eaaa6184 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -149,6 +149,11 @@ export interface RepositoryUIState { readonly onDidChange: Event; } +export interface RepositoryAccessDetails { + readonly rootUri: Uri; + readonly lastAccessTime: number; +} + /** * Log options. */ @@ -399,6 +404,7 @@ export interface API { readonly onDidPublish: Event; readonly git: Git; readonly repositories: Repository[]; + readonly recentRepositories: Iterable; readonly onDidOpenRepository: Event; readonly onDidCloseRepository: Event; diff --git a/extensions/git/src/repositoryCache.ts b/extensions/git/src/repositoryCache.ts index bf5d74b6c68..ed762eb4d94 100644 --- a/extensions/git/src/repositoryCache.ts +++ b/extensions/git/src/repositoryCache.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LogOutputChannel, Memento, workspace } from 'vscode'; +import { LogOutputChannel, Memento, Uri, workspace } from 'vscode'; import { LRUCache } from './cache'; -import { Remote } from './api/git'; +import { Remote, RepositoryAccessDetails } from './api/git'; import { isDescendant } from './util'; export interface RepositoryCacheInfo { @@ -40,6 +40,33 @@ export class RepositoryCache { // Outer LRU: repoUrl -> inner LRU (folderPathOrWorkspaceFile -> RepositoryCacheInfo). private readonly lru = new LRUCache>(RepositoryCache.MAX_REPO_ENTRIES); + private _recentRepositories: Map | undefined; + + get recentRepositories(): Iterable { + if (!this._recentRepositories) { + this._recentRepositories = new Map(); + + for (const [_, inner] of this.lru) { + for (const [folderPath, folderDetails] of inner) { + if (!folderDetails.lastTouchedTime) { + continue; + } + + // Check whether the repository exists with a more recent access time + const repositoryLastAccessTime = this._recentRepositories.get(folderPath); + if (repositoryLastAccessTime && folderDetails.lastTouchedTime <= repositoryLastAccessTime) { + continue; + } + + this._recentRepositories.set(folderPath, folderDetails.lastTouchedTime); + } + } + } + + return Array.from(this._recentRepositories.entries()).map(([rootUri, lastAccessTime]) => + ({ rootUri: Uri.file(rootUri), lastAccessTime } satisfies RepositoryAccessDetails)); + } + constructor(public readonly _globalState: Memento, private readonly _logger: LogOutputChannel) { this.load(); } @@ -193,5 +220,9 @@ export class RepositoryCache { serialized.push([repo, folders]); } void this._globalState.update(RepositoryCache.STORAGE_KEY, serialized); + + // Invalidate recent repositories map + this._recentRepositories?.clear(); + this._recentRepositories = undefined; } }