Browse Source

Fixes the Commit Graph in Live Share sessions

main
Eric Amodio 1 year ago
parent
commit
135feaf6fc
6 changed files with 184 additions and 60 deletions
  1. +10
    -0
      src/env/browser/providers.ts
  2. +24
    -1
      src/env/node/git/vslsGitProvider.ts
  3. +12
    -2
      src/env/node/providers.ts
  4. +21
    -1
      src/vsls/guest.ts
  5. +99
    -56
      src/vsls/host.ts
  6. +18
    -0
      src/vsls/protocol.ts

+ 10
- 0
src/env/browser/providers.ts View File

@ -8,6 +8,16 @@ export function git(_options: GitCommandOptions, ..._args: any[]): Promise
return Promise.resolve('');
}
export function gitLogStreamTo(
_repoPath: string,
_sha: string,
_limit: number,
_options?: { configs?: readonly string[]; stdin?: string },
..._args: string[]
): Promise<[data: string[], count: number]> {
return Promise.resolve([[''], 0]);
}
export async function getSupportedGitProviders(container: Container): Promise<GitProvider[]> {
return [new GitHubGitProvider(container)];
}

+ 24
- 1
src/env/node/git/vslsGitProvider.ts View File

@ -1,7 +1,8 @@
import type { ChildProcess } from 'child_process';
import { FileType, Uri, workspace } from 'vscode';
import { Schemes } from '../../../constants';
import { Container } from '../../../container';
import type { GitCommandOptions } from '../../../git/commandOptions';
import type { GitCommandOptions, GitSpawnOptions } from '../../../git/commandOptions';
import type { GitProviderDescriptor } from '../../../git/gitProvider';
import { GitProviderId } from '../../../git/gitProvider';
import type { Repository } from '../../../git/models/repository';
@ -31,6 +32,28 @@ export class VslsGit extends Git {
return guest.git<TOut>(options, ...args);
}
// eslint-disable-next-line @typescript-eslint/require-await
override async gitSpawn(_options: GitSpawnOptions, ..._args: any[]): Promise<ChildProcess> {
debugger;
throw new Error('Git spawn not supported in Live Share');
}
override async logStreamTo(
repoPath: string,
sha: string,
limit: number,
options?: { configs?: readonly string[]; stdin?: string },
...args: string[]
): Promise<[data: string[], count: number]> {
const guest = await Container.instance.vsls.guest();
if (guest == null) {
debugger;
throw new Error('No guest');
}
return guest.gitLogStreamTo(repoPath, sha, limit, options, ...args);
}
}
export class VslsGitProvider extends LocalGitProvider {

+ 12
- 2
src/env/node/providers.ts View File

@ -15,8 +15,18 @@ function ensureGit() {
return gitInstance;
}
export function git(_options: GitCommandOptions, ..._args: any[]): Promise<string | Buffer> {
return ensureGit().git(_options, ..._args);
export function git(options: GitCommandOptions, ...args: any[]): Promise<string | Buffer> {
return ensureGit().git(options, ...args);
}
export function gitLogStreamTo(
repoPath: string,
sha: string,
limit: number,
options?: { configs?: readonly string[]; stdin?: string },
...args: string[]
): Promise<[data: string[], count: number]> {
return ensureGit().logStreamTo(repoPath, sha, limit, options, ...args);
}
export async function getSupportedGitProviders(container: Container): Promise<GitProvider[]> {

+ 21
- 1
src/vsls/guest.ts View File

@ -8,7 +8,7 @@ import { Logger } from '../system/logger';
import { getLogScope } from '../system/logger.scope';
import { VslsHostService } from './host';
import type { RepositoryProxy, RequestType } from './protocol';
import { GetRepositoriesForUriRequestType, GitCommandRequestType } from './protocol';
import { GetRepositoriesForUriRequestType, GitCommandRequestType, GitLogStreamToCommandRequestType } from './protocol';
export class VslsGuestService implements Disposable {
@log()
@ -70,6 +70,26 @@ export class VslsGuestService implements Disposable {
}
@log()
async gitLogStreamTo(
repoPath: string,
sha: string,
limit: number,
options?: { configs?: readonly string[]; stdin?: string },
...args: string[]
): Promise<[data: string[], count: number]> {
const response = await this.sendRequest(GitLogStreamToCommandRequestType, {
__type: 'gitlens',
repoPath: repoPath,
sha: sha,
limit: limit,
options: options,
args: args,
});
return [response.data, response.count];
}
@log()
async getRepositoriesForUri(uri: Uri): Promise<RepositoryProxy[]> {
const response = await this.sendRequest(GetRepositoriesForUriRequestType, {
__type: 'gitlens',

+ 99
- 56
src/vsls/host.ts View File

@ -1,6 +1,6 @@
import type { CancellationToken, WorkspaceFoldersChangeEvent } from 'vscode';
import { Disposable, Uri, workspace } from 'vscode';
import { git } from '@env/providers';
import { git, gitLogStreamTo } from '@env/providers';
import type { LiveShare, SharedService } from '../@types/vsls';
import type { Container } from '../container';
import { debug, log } from '../system/decorators/log';
@ -13,10 +13,12 @@ import type {
GetRepositoriesForUriResponse,
GitCommandRequest,
GitCommandResponse,
GitLogStreamToCommandRequest,
GitLogStreamToCommandResponse,
RepositoryProxy,
RequestType,
} from './protocol';
import { GetRepositoriesForUriRequestType, GitCommandRequestType } from './protocol';
import { GetRepositoriesForUriRequestType, GitCommandRequestType, GitLogStreamToCommandRequestType } from './protocol';
const defaultWhitelistFn = () => true;
const gitWhitelist = new Map<string, (args: any[]) => boolean>([
@ -42,6 +44,7 @@ const gitWhitelist = new Map boolean>([
['status', defaultWhitelistFn],
['symbolic-ref', defaultWhitelistFn],
['tag', args => args[1] === '-l'],
['worktree', args => args[1] === 'list'],
]);
const leadingSlashRegex = /^[/|\\]/;
@ -76,6 +79,7 @@ export class VslsHostService implements Disposable {
this._disposable = Disposable.from(workspace.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this));
this.onRequest(GitCommandRequestType, this.onGitCommandRequest.bind(this));
this.onRequest(GitLogStreamToCommandRequestType, this.onGitLogStreamToCommandRequest.bind(this));
this.onRequest(GetRepositoriesForUriRequestType, this.onGetRepositoriesForUriRequest.bind(this));
this.onWorkspaceFoldersChanged();
@ -142,65 +146,16 @@ export class VslsHostService implements Disposable {
request: GitCommandRequest,
_cancellation: CancellationToken,
): Promise<GitCommandResponse> {
const { options, args } = request;
const fn = gitWhitelist.get(request.args[0]);
if (fn == null || !fn(request.args)) throw new Error(`Git ${request.args[0]} command is not allowed`);
let isRootWorkspace = false;
if (options.cwd != null && options.cwd.length > 0 && this._sharedToLocalPaths != null) {
// This is all so ugly, but basically we are converting shared paths to local paths
if (this._sharedPathsRegex?.test(options.cwd)) {
options.cwd = normalizePath(options.cwd).replace(this._sharedPathsRegex, (match, shared: string) => {
if (!isRootWorkspace) {
isRootWorkspace = shared === '/~0';
}
const local = this._sharedToLocalPaths.get(shared);
return local != null ? local : shared;
});
} else if (leadingSlashRegex.test(options.cwd)) {
const localCwd = this._sharedToLocalPaths.get('vsls:/~0');
if (localCwd != null) {
isRootWorkspace = true;
options.cwd = normalizePath(this.container.git.getAbsoluteUri(options.cwd, localCwd).fsPath);
}
}
}
let files = false;
let i = -1;
for (const arg of args) {
i++;
if (arg === '--') {
files = true;
continue;
}
if (!files) continue;
if (typeof arg === 'string') {
// If we are the "root" workspace, then we need to remove the leading slash off the path (otherwise it will not be treated as a relative path)
if (isRootWorkspace && leadingSlashRegex.test(arg[0])) {
args.splice(i, 1, arg.substr(1));
}
if (this._sharedPathsRegex?.test(arg)) {
args.splice(
i,
1,
normalizePath(arg).replace(this._sharedPathsRegex, (match, shared: string) => {
const local = this._sharedToLocalPaths.get(shared);
return local != null ? local : shared;
}),
);
}
}
}
const { options, args } = request;
const [cwd, isRootWorkspace] = this.convertGitCommandCwd(options.cwd);
options.cwd = cwd;
let data = await git(options, ...args);
let data = await git(options, ...this.convertGitCommandArgs(args, isRootWorkspace));
if (typeof data === 'string') {
// And then we convert local paths to shared paths
// Convert local paths to shared paths
if (this._localPathsRegex != null && data.length > 0) {
data = data.replace(this._localPathsRegex, (match, local: string) => {
const shared = this._localToSharedPaths.get(normalizePath(local));
@ -214,6 +169,33 @@ export class VslsHostService implements Disposable {
return { data: data.toString('binary'), isBuffer: true };
}
@log()
private async onGitLogStreamToCommandRequest(
request: GitLogStreamToCommandRequest,
_cancellation: CancellationToken,
): Promise<GitLogStreamToCommandResponse> {
const { options, args } = request;
const [cwd, isRootWorkspace] = this.convertGitCommandCwd(request.repoPath);
let [data, count] = await gitLogStreamTo(
cwd,
request.sha,
request.limit,
options,
...this.convertGitCommandArgs(args, isRootWorkspace),
);
if (this._localPathsRegex != null && data.length > 0) {
// Convert local paths to shared paths
data = data.map(d =>
d.replace(this._localPathsRegex!, (match, local: string) => {
const shared = this._localToSharedPaths.get(normalizePath(local));
return shared != null ? shared : local;
}),
);
}
return { data: data, count: count };
}
// eslint-disable-next-line @typescript-eslint/require-await
@log()
private async onGetRepositoriesForUriRequest(
@ -271,6 +253,67 @@ export class VslsHostService implements Disposable {
return sharedUri;
}
private convertGitCommandCwd(cwd: string): [cwd: string, root: boolean];
private convertGitCommandCwd(cwd: string | undefined): [cwd: string | undefined, root: boolean];
private convertGitCommandCwd(cwd: string | undefined): [cwd: string | undefined, root: boolean] {
let isRootWorkspace = false;
if (cwd != null && cwd.length > 0 && this._sharedToLocalPaths != null) {
// This is all so ugly, but basically we are converting shared paths to local paths
if (this._sharedPathsRegex?.test(cwd)) {
cwd = normalizePath(cwd).replace(this._sharedPathsRegex, (match, shared: string) => {
if (!isRootWorkspace) {
isRootWorkspace = shared === '/~0';
}
const local = this._sharedToLocalPaths.get(shared);
return local != null ? local : shared;
});
} else if (leadingSlashRegex.test(cwd)) {
const localCwd = this._sharedToLocalPaths.get('vsls:/~0');
if (localCwd != null) {
isRootWorkspace = true;
cwd = normalizePath(this.container.git.getAbsoluteUri(cwd, localCwd).fsPath);
}
}
}
return [cwd, isRootWorkspace];
}
private convertGitCommandArgs(args: any[], isRootWorkspace: boolean): any[] {
let files = false;
let i = -1;
for (const arg of args) {
i++;
if (arg === '--') {
files = true;
continue;
}
if (!files) continue;
if (typeof arg === 'string') {
// If we are the "root" workspace, then we need to remove the leading slash off the path (otherwise it will not be treated as a relative path)
if (isRootWorkspace && leadingSlashRegex.test(arg[0])) {
args.splice(i, 1, arg.substr(1));
}
if (this._sharedPathsRegex?.test(arg)) {
args.splice(
i,
1,
normalizePath(arg).replace(this._sharedPathsRegex, (match, shared: string) => {
const local = this._sharedToLocalPaths.get(shared);
return local != null ? local : shared;
}),
);
}
}
}
return args;
}
private convertSharedUriToLocal(sharedUri: Uri) {
if (isVslsRoot(sharedUri.path)) {
sharedUri = sharedUri.with({ path: `${sharedUri.path}/` });

+ 18
- 0
src/vsls/protocol.ts View File

@ -17,6 +17,24 @@ export interface GitCommandResponse {
export const GitCommandRequestType = new RequestType<GitCommandRequest, GitCommandResponse>('git');
export interface GitLogStreamToCommandRequest {
repoPath: string;
sha: string;
limit: number;
options?: { configs?: readonly string[]; stdin?: string };
args: string[];
}
export interface GitLogStreamToCommandResponse {
data: string[];
count: number;
}
export const GitLogStreamToCommandRequestType = new RequestType<
GitLogStreamToCommandRequest,
GitLogStreamToCommandResponse
>('git/logStreamTo');
export interface RepositoryProxy {
folderUri: string;
/** @deprecated */

Loading…
Cancel
Save