-
Notifications
You must be signed in to change notification settings - Fork 209
/
workspace-repo-mapper.ts
158 lines (146 loc) · 5.75 KB
/
workspace-repo-mapper.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import { graphqlClient, isError, logDebug } from '@sourcegraph/cody-shared'
import * as vscode from 'vscode'
import { getCodebaseFromWorkspaceUri, gitAPI } from '../repository/git-extension-api'
import type { CodebaseRepoIdMapper } from './enterprise-context-factory'
import { RemoteSearch } from './remote-search'
import type { Repo } from './repo-fetcher'
// TODO(dpc): The vscode.git extension has an delay before we can fetch a
// workspace folder's remote. Switch to cody-engine instead of depending on
// vscode.git and this arbitrary delay.
const GIT_REFRESH_DELAY = 2000
// Watches the VSCode workspace roots and maps any it finds to remote repository
// IDs. This depends on the vscode.git extension for mapping git repositories
// to their remotes.
export class WorkspaceRepoMapper implements vscode.Disposable, CodebaseRepoIdMapper {
private changeEmitter = new vscode.EventEmitter<{ name: string; id: string }[]>()
private disposables: vscode.Disposable[] = [this.changeEmitter]
// The workspace repos.
private repos: { name: string; id: string }[] = []
// A cache of results for non-workspace repos. This caches repos that are
// not found, as well as repo IDs.
private nonWorkspaceRepos = new Map<string, string | undefined>()
private started: Promise<void> | undefined
public dispose(): void {
vscode.Disposable.from(...this.disposables).dispose()
this.disposables = []
}
public clientConfigurationDidChange(): void {
if (this.started) {
this.started.then(() => this.updateRepos())
}
}
// CodebaseRepoIdMapper implementation.
public async repoForCodebase(repoName: string): Promise<Repo | undefined> {
if (!repoName) {
return
}
// Check workspace repository list.
const item = this.repos.find(item => item.name === repoName)
if (item) {
return {
id: item.id,
name: item.name,
}
}
// Check cached, non-workspace repository list.
if (this.nonWorkspaceRepos.has(repoName)) {
const id = this.nonWorkspaceRepos.get(repoName)
return id
? {
id,
name: repoName,
}
: undefined
}
const result = await graphqlClient.getRepoId(repoName)
if (isError(result)) {
throw result
}
this.nonWorkspaceRepos.set(repoName, result || undefined)
return result
? {
name: repoName,
id: result,
}
: undefined
}
// Fetches the set of repo IDs and starts listening for workspace changes.
// After this Promise resolves, `workspaceRepoIds` contains the set of
// repo IDs for the workspace (if any.)
public async start(): Promise<void> {
// If are already starting/started, then join that.
if (this.started) {
return this.started
}
this.started = (async () => {
try {
await this.updateRepos()
} catch (error) {
// Reset the started property so the next call to start will try again.
this.started = undefined
throw error
}
vscode.workspace.onDidChangeWorkspaceFolders(
async () => {
logDebug('WorkspaceRepoMapper', 'Workspace folders changed, updating repos')
setTimeout(async () => await this.updateRepos(), GIT_REFRESH_DELAY)
},
undefined,
this.disposables
)
gitAPI()?.onDidOpenRepository(
async () => {
logDebug('WorkspaceRepoMapper', 'vscode.git repositories changed, updating repos')
setTimeout(async () => await this.updateRepos(), GIT_REFRESH_DELAY)
},
undefined,
this.disposables
)
})()
return this.started
}
public get workspaceRepos(): { name: string; id: string }[] {
return [...this.repos]
}
public get onChange(): vscode.Event<{ name: string; id: string }[]> {
return this.changeEmitter.event
}
// Updates the `workspaceRepos` property and fires the change event.
private async updateRepos(): Promise<void> {
try {
const folders = vscode.workspace.workspaceFolders || []
logDebug(
'WorkspaceRepoMapper',
`Mapping ${folders.length} workspace folders to repos: ${folders
.map(f => f.uri.toString())
.join()}`
)
this.repos = await this.findRepoIds(folders)
} catch (error) {
logDebug('WorkspaceRepoMapper', `Error mapping workspace folders to repo IDs: ${error}`)
throw error
}
this.changeEmitter.fire(this.workspaceRepos)
}
// Given a set of workspace folders, looks up their git remotes and finds the related repo IDs,
// if any.
private async findRepoIds(
folders: readonly vscode.WorkspaceFolder[]
): Promise<{ name: string; id: string }[]> {
const repoNames = new Set(
folders.flatMap(folder => {
const codebase = getCodebaseFromWorkspaceUri(folder.uri)
return codebase ? [codebase] : []
})
)
if (repoNames.size === 0) {
// Otherwise we fetch the first 10 repos from the Sourcegraph instance
return []
}
const ids = await graphqlClient.getRepoIds([...repoNames.values()], RemoteSearch.MAX_REPO_COUNT)
if (isError(ids)) {
throw ids
}
return ids
}
}