Skip to content

Commit

Permalink
Cody Ignore: refactor the Git extension API usage and document relate…
Browse files Browse the repository at this point in the history
…d TODOs
  • Loading branch information
valerybugakov committed May 10, 2024
1 parent e01888c commit 2da8d02
Show file tree
Hide file tree
Showing 16 changed files with 612 additions and 445 deletions.
8 changes: 0 additions & 8 deletions agent/src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { spawn } from 'node:child_process'
import * as fspromises from 'node:fs/promises'
import path from 'node:path'

import type { Polly, Request } from '@pollyjs/core'
Expand Down Expand Up @@ -85,13 +84,6 @@ export async function initializeVscodeExtension(
extensionClient: ExtensionClient
): Promise<void> {
const paths = envPaths('Cody')
try {
const gitdirPath = path.join(workspaceRoot.fsPath, '.git')
await fspromises.stat(gitdirPath)
vscode_shim.addGitRepository(workspaceRoot, 'fake_vscode_shim_commit')
} catch {
/* ignore */
}
const context: vscode.ExtensionContext = {
asAbsolutePath(relativePath) {
return path.resolve(workspaceRoot.fsPath, relativePath)
Expand Down
5 changes: 4 additions & 1 deletion agent/src/bfg/BfgRetriever.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ describe('BfgRetriever', async () => {
})

const rootUri = vscode.Uri.from({ scheme: 'file', path: gitdir })
vscode_shim.addGitRepository(rootUri, 'asdf')
// TODO: git extension APIs used in the BFG retriever are not supported by the agent.
// To fix this test the following functionality should be implemented:
// - https://github.com/sourcegraph/cody/issues/4137
// - https://github.com/sourcegraph/cody/issues/4138
const agent = await newEmbeddedAgentClient({
name: 'BfgContextFetcher',
version: '0.1.0',
Expand Down
111 changes: 6 additions & 105 deletions agent/src/vscode-shim.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { execSync } from 'node:child_process'
import path from 'node:path'

import * as uuid from 'uuid'
Expand All @@ -21,7 +20,6 @@ import { logDebug, logError, setClientNameVersion } from '@sourcegraph/cody-shar
// at Module._compile (pkg/prelude/bootstrap.js:1926:22)
// </VERY IMPORTANT>
import type { InlineCompletionItemProvider } from '../../vscode/src/completions/inline-completion-item-provider'
import type { API, GitExtension, Repository } from '../../vscode/src/repository/builtinGitExtension'
import { AgentEventEmitter as EventEmitter } from '../../vscode/src/testutils/AgentEventEmitter'
import { emptyEvent } from '../../vscode/src/testutils/emptyEvent'
import {
Expand All @@ -30,7 +28,6 @@ import {
CommentThreadCollapsibleState,
// It's OK to import the VS Code mocks because they don't depend on the 'vscode' module.
Disposable,
ExtensionKind,
FileType,
LogLevel,
ProgressLocation,
Expand Down Expand Up @@ -315,6 +312,8 @@ const _workspace: typeof vscode.workspace = {
name: workspaceDocuments.workspaceRootUri?.path,
}
},
// TODO: used by `WorkspaceRepoMapper` and will be used by `git.onDidOpenRepository`
// https://github.com/sourcegraph/cody/issues/4136
onDidChangeWorkspaceFolders: emptyEvent(),
onDidOpenTextDocument: onDidOpenTextDocument.event,
onDidChangeConfiguration: onDidChangeConfiguration.event,
Expand Down Expand Up @@ -346,7 +345,9 @@ const _workspace: typeof vscode.workspace = {
}
return relativePath
},
createFileSystemWatcher: () => emptyFileWatcher, // TODO: used for codyignore and custom commands
// TODO: used for Cody Ignore, WorkspaceRepoMapper and custom commands
// https://github.com/sourcegraph/cody/issues/4136
createFileSystemWatcher: () => emptyFileWatcher,
getConfiguration: (section, scope): vscode.WorkspaceConfiguration => {
if (section !== undefined) {
if (scope === undefined) {
Expand Down Expand Up @@ -748,111 +749,11 @@ const _window: typeof vscode.window = {
}

export const window = _window
const gitRepositories: Repository[] = []

export function gitRepository(uri: vscode.Uri, headCommit: string): Repository {
const repo: Partial<Repository> = {
rootUri: uri,
ui: { selected: false, onDidChange: emptyEvent() },
add: () => Promise.resolve(),
addRemote: () => Promise.resolve(),
apply: () => Promise.resolve(),
checkout: () => Promise.resolve(),
clean: () => Promise.resolve(),
commit: () => Promise.resolve(),
createBranch: () => Promise.resolve(),
deleteBranch: () => Promise.resolve(),
deleteTag: () => Promise.resolve(),
pull: () => Promise.resolve(),
push: () => Promise.resolve(),
diffBlobs: () => Promise.resolve(''),
detectObjectType: () => Promise.resolve({ mimetype: 'mimetype' }),
diffIndexWith: () => Promise.resolve([]) as any,
diff: () => Promise.resolve(''),
diffBetween: () => Promise.resolve('') as any,
blame: () => Promise.resolve(''),
// buffer: () => Promise.resolve(Buffer.apply('', 'utf-8')),
buffer: () => Promise.resolve(Buffer.alloc(0)),
diffIndexWithHEAD: () => Promise.resolve('') as any,
state: {
refs: [],
indexChanges: [],
mergeChanges: [],
onDidChange: emptyEvent(),
remotes: [],
submodules: [],
workingTreeChanges: [],
rebaseCommit: undefined,
HEAD: {
type: /* RefType.Head */ 0, // Can't reference RefType.Head because it's from a d.ts file
commit: headCommit,
},
},
}
return repo as Repository
}
export function addGitRepository(uri: vscode.Uri, headCommit: string): void {
gitRepositories.push(gitRepository(uri, headCommit))
}

const gitExports: GitExtension = {
enabled: true,
onDidChangeEnablement: emptyEvent(),
getAPI(version) {
const api: Partial<API> = {
repositories: gitRepositories,
onDidChangeState: emptyEvent(),
onDidCloseRepository: emptyEvent(),
onDidOpenRepository: emptyEvent(),
onDidPublish: emptyEvent(),
getRepository(uri) {
try {
const cwd = uri.fsPath
const toplevel = execSync('git rev-parse --show-toplevel', {
cwd,
stdio: 'pipe',
})
.toString()
.trim()
if (toplevel !== uri.fsPath) {
return null
}
const commit = execSync('git rev-parse --abbrev-ref HEAD', {
cwd,
stdio: 'pipe',
})
.toString()
.trim()
return gitRepository(Uri.file(toplevel), commit)
} catch {
return null
}
},
}
return api as API
},
}
const gitExtension: vscode.Extension<GitExtension> = {
activate: () => Promise.resolve(gitExports),
extensionKind: ExtensionKind.Workspace,
extensionPath: 'extensionPath.doNotReadFromHere',
extensionUri: Uri.file('extensionPath.doNotReadFromHere'),
id: 'vscode.git',
packageJSON: {},
isActive: true,
exports: gitExports,
}

const _extensions: typeof vscode.extensions = {
all: [gitExtension],
all: [],
onDidChange: emptyEvent(),
getExtension: (extensionId: string) => {
const shouldActivateGitExtension =
clientInfo !== undefined && clientInfo?.capabilities?.git !== 'disabled'
if (shouldActivateGitExtension && extensionId === 'vscode.git') {
const extension: vscode.Extension<any> = gitExtension
return extension
}
return undefined
},
}
Expand Down
6 changes: 4 additions & 2 deletions vscode/src/chat/chat-view/CodebaseStatusProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { getConfiguration } from '../../configuration'
import type { CodebaseRepoIdMapper } from '../../context/enterprise-context-factory'
import { getEditor } from '../../editor/active-editor'
import type { SymfRunner } from '../../local-context/symf'
import { getCodebaseFromWorkspaceUri } from '../../repository/git-extension-api'
import { RepoMetadatafromGitApi } from '../../repository/repo-metadata-from-git-api'
import { repoNameResolver } from '../../repository/repo-name-resolver'

interface CodebaseIdentifiers {
localFolder: vscode.Uri
Expand Down Expand Up @@ -157,7 +157,9 @@ export class CodebaseStatusProvider implements vscode.Disposable, ContextStatusP
// Always use the codebase from config as this is manually set by the user
newCodebase.remote =
config.codebase ||
(currentFile ? getCodebaseFromWorkspaceUri(currentFile) : config.codebase)
(currentFile
? (await repoNameResolver.getRepoNamesFromWorkspaceUri(currentFile))[0]
: config.codebase)
if (newCodebase.remote) {
newCodebase.remoteRepoId = (
await this.codebaseRepoIdMapper?.repoForCodebase(newCodebase.remote)
Expand Down
17 changes: 12 additions & 5 deletions vscode/src/completions/context/retrievers/bfg/bfg-retriever.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { spawnBfg } from '../../../../graph/bfg/spawn-bfg'
import type { MessageHandler } from '../../../../jsonrpc/jsonrpc'
import { logDebug } from '../../../../log'
import type { Repository } from '../../../../repository/builtinGitExtension'
import { gitAPI } from '../../../../repository/git-extension-api'
import { vscodeGitAPI } from '../../../../repository/git-extension-api'
import { captureException } from '../../../../services/sentry/sentry'
import type { ContextRetriever, ContextRetrieverOptions } from '../../../types'

Expand Down Expand Up @@ -70,15 +70,22 @@ export class BfgRetriever implements ContextRetriever {
}
}
private async indexGitRepositories(): Promise<void> {
const git = gitAPI()
if (!git) {
// TODO: Only works in the VS Code extension where the Git extension is available.
// We should implement fallbacks for the Git methods used below.
if (!vscodeGitAPI) {
return
}
for (const repository of git.repositories) {

// TODO: scan workspace folders for git repos to support this in the agent.
// https://github.com/sourcegraph/cody/issues/4137
for (const repository of vscodeGitAPI.repositories) {
await this.didChangeGitExtensionRepository(repository)
}
this.context.subscriptions.push(
git.onDidOpenRepository(repository => this.didChangeGitExtensionRepository(repository))
// TODO: https://github.com/sourcegraph/cody/issues/4138
vscodeGitAPI.onDidOpenRepository(repository =>
this.didChangeGitExtensionRepository(repository)
)
)
this.context.subscriptions.push(
vscode.workspace.onDidChangeWorkspaceFolders(() => this.indexInferredGitRepositories())
Expand Down
4 changes: 2 additions & 2 deletions vscode/src/context/remote-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '@sourcegraph/cody-shared'

import type { URI } from 'vscode-uri'
import { getCodebaseFromWorkspaceUri } from '../repository/git-extension-api'
import { repoNameResolver } from '../repository/repo-name-resolver'
import type * as repofetcher from './repo-fetcher'

export enum RepoInclusion {
Expand Down Expand Up @@ -131,7 +131,7 @@ export class RemoteSearch implements ContextStatusProvider, IRemoteSearch {
// IRemoteSearch implementation. This is only used for inline edit context.

public async setWorkspaceUri(uri: URI): Promise<void> {
const codebase = getCodebaseFromWorkspaceUri(uri)
const [codebase] = await repoNameResolver.getRepoNamesFromWorkspaceUri(uri)
if (!codebase) {
this.setRepos([], RepoInclusion.Automatic)
return
Expand Down
20 changes: 12 additions & 8 deletions vscode/src/context/workspace-repo-mapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { graphqlClient, isError, logDebug } from '@sourcegraph/cody-shared'
import * as vscode from 'vscode'
import { getCodebaseFromWorkspaceUri, gitAPI } from '../repository/git-extension-api'
import { vscodeGitAPI } from '../repository/git-extension-api'
import { repoNameResolver } from '../repository/repo-name-resolver'
import type { CodebaseRepoIdMapper } from './enterprise-context-factory'
import { RemoteSearch } from './remote-search'
import type { Repo } from './repo-fetcher'
Expand Down Expand Up @@ -95,7 +96,9 @@ export class WorkspaceRepoMapper implements vscode.Disposable, CodebaseRepoIdMap
undefined,
this.disposables
)
gitAPI()?.onDidOpenRepository(
// TODO: Only works in the VS Code extension where the Git extension is available.
// https://github.com/sourcegraph/cody/issues/4138
vscodeGitAPI?.onDidOpenRepository(
async () => {
logDebug('WorkspaceRepoMapper', 'vscode.git repositories changed, updating repos')
setTimeout(async () => await this.updateRepos(), GIT_REFRESH_DELAY)
Expand Down Expand Up @@ -137,18 +140,19 @@ export class WorkspaceRepoMapper implements vscode.Disposable, CodebaseRepoIdMap
// Given a set of workspace folders, looks up their git remotes and finds the related repo IDs,
// if any.
private async findRepos(folders: readonly vscode.WorkspaceFolder[]): Promise<Repo[]> {
const repoNames = new Set(
folders.flatMap(folder => {
const codebase = getCodebaseFromWorkspaceUri(folder.uri)
return codebase ? [codebase] : []
const repoNames = await Promise.all(
folders.map(folder => {
return repoNameResolver.getRepoNamesFromWorkspaceUri(folder.uri)
})
)
if (repoNames.size === 0) {

const uniqueRepoNames = new Set(repoNames.flat())
if (uniqueRepoNames.size === 0) {
// Otherwise we fetch the first 10 repos from the Sourcegraph instance
return []
}
const repos = await graphqlClient.getRepoIds(
[...repoNames.values()],
[...uniqueRepoNames.values()],
RemoteSearch.MAX_REPO_COUNT
)
if (isError(repos)) {
Expand Down
1 change: 0 additions & 1 deletion vscode/src/jsonrpc/agent-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,6 @@ interface ClientCapabilities {
completions?: 'none'
// When 'streaming', handles 'chat/updateMessageInProgress' streaming notifications.
chat?: 'none' | 'streaming'
git?: 'none' | 'disabled'
// If 'enabled', the client must implement the progress/start,
// progress/report, and progress/end notification endpoints.
progressBars?: 'none' | 'enabled'
Expand Down
16 changes: 7 additions & 9 deletions vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ import { getChatModelsFromConfiguration, syncModelProviders } from './models/uti
import type { FixupTask } from './non-stop/FixupTask'
import { CodyProExpirationNotifications } from './notifications/cody-pro-expiration'
import { showSetupNotification } from './notifications/setup-notification'
import { enterpriseRepoNameResolver } from './repository/enterprise-repo-name-resolver'
import { gitAPIinit } from './repository/git-extension-api'
import { initVSCodeGitApi } from './repository/git-extension-api'
import { repoNameResolver } from './repository/repo-name-resolver'
import { SearchViewProvider } from './search/SearchViewProvider'
import { AuthProvider } from './services/AuthProvider'
import { CharactersLogger } from './services/CharactersLogger'
Expand Down Expand Up @@ -148,8 +148,7 @@ const register = async (
// from the subsequent initialization.
disposables.push(manageDisplayPathEnvInfoForExtension())

// Set codyignore list after git extension startup
disposables.push(await gitAPIinit())
disposables.push(await initVSCodeGitApi())

const isExtensionModeDevOrTest =
context.extensionMode === vscode.ExtensionMode.Development ||
Expand Down Expand Up @@ -216,9 +215,9 @@ const register = async (
)
disposables.push(contextFiltersProvider)
disposables.push(contextProvider)
const bindedRepoNamesResolver =
enterpriseRepoNameResolver.getRepoNamesFromWorkspaceUri.bind(enterpriseRepoNameResolver)
await contextFiltersProvider.init(bindedRepoNamesResolver).then(() => contextProvider.init())
await contextFiltersProvider
.init(repoNameResolver.getRepoNamesFromWorkspaceUri)
.then(() => contextProvider.init())

// Shared configuration that is required for chat views to send and receive messages
const messageProviderOptions: MessageProviderOptions = {
Expand Down Expand Up @@ -270,10 +269,9 @@ const register = async (
githubClient.onConfigurationChange({ authToken: initialConfig.experimentalGithubAccessToken })
promises.push(
contextFiltersProvider
.init(bindedRepoNamesResolver)
.init(repoNameResolver.getRepoNamesFromWorkspaceUri)
.then(() => contextProvider.onConfigurationChange(newConfig))
)
promises.push(contextFiltersProvider.init(bindedRepoNamesResolver))
externalServicesOnDidConfigurationChange(newConfig)
promises.push(configureEventsInfra(newConfig, isExtensionModeDevOrTest, authProvider))
platform.onConfigurationChange?.(newConfig)
Expand Down

0 comments on commit 2da8d02

Please sign in to comment.