From 313eac47d6325861444d89f684a5da75dbd51350 Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Fri, 2 Nov 2018 00:44:26 -0700 Subject: [PATCH 1/2] wip --- src/client/controller.ts | 2 + src/client/fileSystem.ts | 143 +++++++++++++++++++++++++++++++++++++++ src/context.ts | 2 +- 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/client/fileSystem.ts diff --git a/src/client/controller.ts b/src/client/controller.ts index 692ffeb..5735f5c 100644 --- a/src/client/controller.ts +++ b/src/client/controller.ts @@ -14,6 +14,7 @@ import { ConfiguredExtension, isExtensionEnabled } from '../extensions/extension import { ExtensionManifest } from '../schema/extension.schema' import { ConfigurationCascade, ConfigurationSubject, Settings } from '../settings' import { registerBuiltinClientCommands, updateConfiguration } from './clientCommands' +import { getFileSystem } from './fileSystem' import { log } from './log' /** @@ -127,6 +128,7 @@ export function createController createMessageTransports(extension), }), environmentFilter, + getFileSystem: uri => getFileSystem(uri, (query, vars) => context.queryGraphQL(query, vars, true)), }) // Apply trace settings. diff --git a/src/client/fileSystem.ts b/src/client/fileSystem.ts new file mode 100644 index 0000000..5aa6a60 --- /dev/null +++ b/src/client/fileSystem.ts @@ -0,0 +1,143 @@ +import { Subscribable } from 'rxjs' +import * as sourcegraph from 'sourcegraph' +import { createAggregateError } from '../errors' +import { QueryResult } from '../graphql' +import * as GQL from '../schema/graphqlschema' + +/** + * Returns a {@link module:sourcegraph.FileSystem} that can access files and directories at URIs for extensions by + * querying the Sourcegraph GraphQL API. + * + * @todo Make this more efficient by using a Zip archive. + * @todo Make this work for repositories that aren't on Sourcegraph by accessing code host APIs directly. + */ +export function getFileSystem( + uri: string, + queryGraphQL: ( + query: string, + vars: { [name: string]: any } + ) => Subscribable>> +): sourcegraph.FileSystem { + // Only supports URLs of the format `git://repo?rev#path`. + if (!uri.startsWith('git:')) { + throw new Error(`only git: URIs are supported (got ${JSON.stringify(uri)})`) + } + + return { + readDirectory: async uri => { + const { repo, rev, path } = resolveURI(uri.toString()) + const commit = getCommitData( + await toPromise( + queryGraphQL( + ` + query ReadDirectory($repoName: String!, $rev: String!, $path: String!) { + repository(name: $repoName) { + commit(rev: $rev) { + tree(path: $path) { + entries(recursive: false) { + name + isDirectory + } + } + } + } + } + `, + { repo, rev, path } + ) + ) + ) + if (!commit.tree) { + throw new Error(`path not found: ${path} (in ${repo}@${rev})`) + } + return commit.tree.entries.map( + ({ name, isDirectory }) => + [name, isDirectory ? sourcegraph.FileType.Directory : sourcegraph.FileType.File] as [ + string, + sourcegraph.FileType + ] + ) + }, + readFile: async uri => { + const { repo, rev, path } = resolveURI(uri.toString()) + const commit = getCommitData( + await toPromise( + queryGraphQL( + ` + query ReadDirectory($repoName: String!, $rev: String!, $path: String!) { + repository(name: $repoName) { + commit(rev: $rev) { + blob(path: $path) { + content + } + } + } + } + `, + { repo, rev, path } + ) + ) + ) + if (!commit.blob) { + throw new Error(`path not found: ${path} (in ${repo}@${rev})`) + } + return new TextEncoder().encode(commit.blob.content) + }, + } +} + +function getCommitData({ data, errors }: QueryResult>): GQL.IGitCommit { + if (errors && errors.length > 0) { + throw createAggregateError(errors) + } + if (!data) { + throw new Error('no data') + } + if (!data.repository) { + throw new Error('repository not found') + } + if (!data.repository.commit) { + throw new Error('commit not found') + } + return data.repository.commit +} + +/** + * A resolved URI identifies a path in a repository at a specific revision. + */ +interface ResolvedURI { + repo: string + rev: string + path: string +} + +/** + * Resolve a URI of the forms git://github.com/owner/repo?rev#path and file:///path to an absolute reference, using + * the given base (root) URI. + */ +function resolveURI(uri: string): ResolvedURI { + const url = new URL(uri) + if (url.protocol === 'git:') { + return { + repo: (url.host + url.pathname).replace(/^\/*/, '').toLowerCase(), + rev: url.search.slice(1).toLowerCase(), + path: url.hash.slice(1), + } + } + throw new Error(`unrecognized URI: ${JSON.stringify(uri)} (supported URI schemes: git)`) +} + +function toPromise(sub: Subscribable): Promise { + return new Promise((resolve, reject) => { + const unsub = sub.subscribe( + v => { + unsub.unsubscribe() + resolve(v) + }, + err => { + unsub.unsubscribe() + reject(err) + } + ) + }) +} diff --git a/src/context.ts b/src/context.ts index d5cdfa6..d5a376f 100644 --- a/src/context.ts +++ b/src/context.ts @@ -39,7 +39,7 @@ export interface Context { request: string, variables?: { [name: string]: any }, mightContainPrivateInfo?: boolean - ): Subscribable>> + ): Subscribable>> /** * Sends a batch of LSP requests to the Sourcegraph LSP gateway API and returns the result. From b6ccda3de5ae6753f29866cb5aed24a0bec731c6 Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Sat, 3 Nov 2018 03:52:45 -0700 Subject: [PATCH 2/2] wip --- src/client/fileSystem.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/client/fileSystem.ts b/src/client/fileSystem.ts index 5aa6a60..468cfd0 100644 --- a/src/client/fileSystem.ts +++ b/src/client/fileSystem.ts @@ -1,5 +1,6 @@ import { Subscribable } from 'rxjs' import * as sourcegraph from 'sourcegraph' +import { FileType } from 'sourcegraph' import { createAggregateError } from '../errors' import { QueryResult } from '../graphql' import * as GQL from '../schema/graphqlschema' @@ -30,8 +31,8 @@ export function getFileSystem( await toPromise( queryGraphQL( ` - query ReadDirectory($repoName: String!, $rev: String!, $path: String!) { - repository(name: $repoName) { + query ReadDirectory($repo: String!, $rev: String!, $path: String!) { + repository(name: $repo) { commit(rev: $rev) { tree(path: $path) { entries(recursive: false) { @@ -52,10 +53,7 @@ export function getFileSystem( } return commit.tree.entries.map( ({ name, isDirectory }) => - [name, isDirectory ? sourcegraph.FileType.Directory : sourcegraph.FileType.File] as [ - string, - sourcegraph.FileType - ] + [name, isDirectory ? FileType.Directory : FileType.File] as [string, FileType] ) }, readFile: async uri => { @@ -64,8 +62,8 @@ export function getFileSystem( await toPromise( queryGraphQL( ` - query ReadDirectory($repoName: String!, $rev: String!, $path: String!) { - repository(name: $repoName) { + query ReadFile($repo: String!, $rev: String!, $path: String!) { + repository(name: $repo) { commit(rev: $rev) { blob(path: $path) { content