|
|
@ -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}/` }); |
|
|
|